Logo Search packages:      
Sourcecode: firebird1.5 version File versions

sql.cpp

//____________________________________________________________
//  
//          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
//  
//
//____________________________________________________________
//
//    $Id: sql.cpp,v 1.14.2.1 2003/10/30 22:25:53 skidder Exp $
//

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


extern "C" {


#define ERROR_LENGTH                128
#ifdef FLINT_CACHE
#define MIN_CACHE_BUFFERS           250
#define DEF_CACHE_BUFFERS           1000
#endif
#define DEFAULT_BLOB_SEGMENT_LENGTH 80    /* bytes */

extern ACT cur_routine;
extern TEXT *database_name;

TEXT *module_lc_ctype = NULL;

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(SSHORT, BOOLEAN);
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(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(TEXT *);
static ACT act_lock(void);
static ACT act_openclose(enum act_t);
static ACT act_open_blob(ACT_T, SYM);
static ACT act_prepare(void);
static ACT act_procedure(void);
static ACT act_select(void);
static ACT act_set(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 BOOLEAN check_filename(TEXT *);
static void connect_opts(TEXT **, TEXT **, TEXT **, TEXT **, USHORT *);
#ifdef FLINT_CACHE
static FIL define_cache(void);
#endif
static FIL define_file(void);
static FIL define_log_file(BOOLEAN);
static DBB dup_dbb(DBB);
static void error(TEXT *, TEXT *);
static TEXT *extract_string(BOOLEAN);
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, TEXT *);
static GPRE_REL make_relation(GPRE_REQ, 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(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 BOOLEAN par_into(DYN);
static void par_options(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 BOOLEAN par_transaction_modes(GPRE_TRA, BOOLEAN);
static BOOLEAN par_using(DYN);
static USHORT resolve_dtypes(KWWORDS, BOOLEAN);
static BOOLEAN tail_database(enum act_t, DBB);
static void to_upcase(TEXT *, TEXT *);
static void dialect1_bad_type(USHORT);

static SWE whenever[SWE_max], whenever_list;

#define END_OF_COMMAND                                                              \
      (((sw_language != lang_cobol) &&                                        \
            ((int) token.tok_keyword == (int) KW_SEMI_COLON)) ||  \
             ((sw_language == lang_cobol) &&                                  \
             ((int) token.tok_keyword == (int) KW_END_EXEC)))


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

ACT SQL_action(TEXT * base_directory)
{
      ACT action;
      enum kwwords keyword;

      sw_gen_sql = TRUE;

      switch (keyword = token.tok_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:
            ADVANCE_TOKEN;
            break;

      default:
            SYNTAX_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((USHORT) 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;
      }

      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)
{
      ULONG field_length;


      if (field->fld_dtype <= dtype_any_text) {
            /* 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 = (sw_cstring
                                                      && !FIELD_ALLOWS_NULLS(field)) ?
                              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_float:
                  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(GDS_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)
{
      USHORT i;

      whenever_list = NULL;

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

      module_lc_ctype = default_lc_ctype;
}


//____________________________________________________________
//  
//  

void SQL_par_field_collate( GPRE_REQ request, GPRE_FLD field)
{
      SYM symbol;

      if (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 (token.tok_type != tok_ident)
                  SYNTAX_ERROR("<collation name>");
            if (!(symbol = MSC_find_symbol(token.tok_symbol, SYM_collate)))
                  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");
            ADVANCE_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, BOOLEAN udf)
{
      int l, p, q;
      enum kwwords keyword;
      SYM symbol;
      char s[ERROR_LENGTH];
      BOOLEAN sql_date = FALSE;

      switch (keyword = token.tok_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:
            ADVANCE_TOKEN;
            break;

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

      case KW_TIME:
            if (sw_sql_dialect == 1)
                  SYNTAX_ERROR("<data type>");
            ADVANCE_TOKEN;
            break;

      case KW_SQL:
            if (sw_sql_dialect == 1)
                  SYNTAX_ERROR("<data type>");
            ADVANCE_TOKEN;
            if (token.tok_keyword == KW_DATE)
                  ADVANCE_TOKEN;
            else
                  SYNTAX_ERROR("<data type>");
            keyword = KW_DATE;
            sql_date = TRUE;
            break;

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

      default:
            if (udf) {
                  if (keyword == KW_CSTRING)
                        ADVANCE_TOKEN;
                  else
                        SYNTAX_ERROR("<data type>");
            }
            else {
                  SQL_resolve_identifier("<domain name>", s);
                  field->fld_global = symbol =
                        MSC_symbol(SYM_field, s, (USHORT) strlen(s), (GPRE_CTX) field);
                  if (!MET_domain_lookup(request, field, s))
                        PAR_error("Specified DOMAIN or source column not found");
                  ADVANCE_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_float;
            break;

      case KW_FLOAT:
            if (MATCH(KW_LEFT_PAREN)) {
                  l = EXP_USHORT_ordinal(TRUE);
                  EXP_match_paren();
                  if (l < 17)
                        field->fld_dtype = dtype_float;
                  else
                        field->fld_dtype = dtype_double;
            }
            else
                  field->fld_dtype = dtype_float;
            break;

      case KW_LONG:
            if (!KEYWORD(KW_FLOAT))
                  SYNTAX_ERROR("FLOAT");
            ADVANCE_TOKEN;
            field->fld_dtype = dtype_double;
            break;

      case KW_DOUBLE:
            if (!KEYWORD(KW_PRECISION))
                  SYNTAX_ERROR("PRECISION");
            ADVANCE_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 (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 (!KEYWORD(KW_CHAR))
                  SYNTAX_ERROR("CHARACTER");
            ADVANCE_TOKEN;
            field->fld_flags |= FLD_national;
            /* Fall into KW_CHAR */
      case KW_CHAR:
            if (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 (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 (MATCH(KW_LEFT_PAREN)) {
                  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 (sw_sql_dialect == SQL_DIALECT_V5)
                              field->fld_dtype = dtype_double;
                        else
                              field->fld_dtype = dtype_int64;
                  }

                  if (MATCH(KW_COMMA)) {
                        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 (MATCH(KW_LEFT_PAREN)) {
                  if (MATCH(KW_COMMA))
                        field->fld_sub_type = EXP_SSHORT_ordinal(TRUE);
                  else {
                        field->fld_seg_length = EXP_USHORT_ordinal(TRUE);
                        if (MATCH(KW_COMMA))
                              field->fld_sub_type = EXP_SSHORT_ordinal(TRUE);
                  }
                  EXP_match_paren();
            }
            else {
                  if (MATCH(KW_SUB_TYPE)) {
                        field->fld_sub_type = PAR_blob_subtype(request->req_database);
                  }
                  if (MATCH(KW_SEGMENT)) {
                        MATCH(KW_SIZE);
                        field->fld_seg_length = EXP_USHORT_ordinal(TRUE);
                  }
            }
            break;

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

//   Check for array declaration 

      if ((keyword != KW_BLOB) && !udf && (MATCH(KW_L_BRCKET))) {
            field->fld_array_info = (struct ary *) ALLOC(sizeof(struct ary));
            par_array(field);
      }

      if (MATCH(KW_CHAR)) {
            SYM symbol;
            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 == BLOB_untyped) field->fld_sub_type =
                        BLOB_text;

            if (field->fld_dtype == dtype_blob
                  && field->fld_sub_type !=
                  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 (!MATCH(KW_SET))
                  SYNTAX_ERROR("SET");
            if (token.tok_type != tok_ident)
                  SYNTAX_ERROR("<character set name>");
            if (!(symbol = MSC_find_symbol(token.tok_symbol, SYM_charset)))
                  PAR_error("The named CHARACTER SET was not found");
            field->fld_character_set = (INTLSYM) symbol->sym_object;
            ADVANCE_TOKEN;
      }

      if (field->fld_flags & FLD_national) {
            if (!
                  (symbol =
                   MSC_find_symbol(HSH_lookup(DEFAULT_CHARACTER_SET_NAME),
                                           SYM_charset)))
         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 == BLOB_text))
                   && !field->fld_character_set && !field->fld_collate && request
                   && request->req_database
                   && request->req_database->dbb_def_charset) {
            /* Use database default character set */
            if (symbol =
                  MSC_find_symbol(HSH_lookup
                                          (request->req_database->dbb_def_charset),
                                          SYM_charset)) 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,
                          TEXT * prc_string,
                          TEXT * db_string, TEXT * owner_string, BOOLEAN err_flag)
{
      DBB db;
      GPRE_PRC procedure, tmp_procedure;
      SYM symbol;
      SCHAR s[ERROR_LENGTH];

      procedure = NULL;

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

            if (!(symbol = MSC_find_symbol(HSH_lookup(db_string), SYM_database)))
                  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;
      }

      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;
            for (db = isc_databases; db; db = db->dbb_next)
                  if (tmp_procedure =
                        MET_get_procedure(db, prc_string,
                                                  owner_string)) 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,
                         TEXT * rel_string,
                         TEXT * db_string, TEXT * owner_string, BOOLEAN err_flag)
{
      DBB db;
      GPRE_REL relation, tmp_relation;
      SYM symbol;
      SCHAR s[ERROR_LENGTH];

      relation = NULL;

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

            if (!(symbol = MSC_find_symbol(HSH_lookup(db_string), SYM_database)))
                  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;
      }

      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;
            for (db = isc_databases; db; db = db->dbb_next)
                  if (tmp_relation = MET_get_relation(db, rel_string, owner_string))
                        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)
{
      SYM symbol;
      TEXT *t_str;

      db_name[0] = 0;
      owner_name[0] = 0;

      t_str = (TEXT *) ALLOC(NAME_SIZE + 1);
      SQL_resolve_identifier("<Table name>", t_str);

      if (symbol = MSC_find_symbol(token.tok_symbol, SYM_database)) {
            strcpy(db_name, symbol->sym_name);
            ADVANCE_TOKEN;
            if (!MATCH(KW_DOT))
                  SYNTAX_ERROR(". (period)");
      }

      SQL_resolve_identifier("<Table name>", t_str);
      if (token.tok_length > NAME_SIZE)
            PAR_error("Table, owner, or database name too long");

      strcpy(r_name, token.tok_string);
      ADVANCE_TOKEN;

      if (MATCH(KW_DOT)) {
            /* the table name was really a owner specifier */

            if (token.tok_length > NAME_SIZE)
                  PAR_error("TABLE name too long");
            strcpy(owner_name, r_name);
            SQL_resolve_identifier("<Table name>", t_str);
            strcpy(r_name, token.tok_string);
            ADVANCE_TOKEN;
      }
}


//____________________________________________________________
//  
//          Extract SQL va
//  

TEXT *SQL_var_or_string(BOOLEAN string_only)
{

      if ((!SINGLE_QUOTED(token.tok_type) && sw_sql_dialect == 3) ||
            (!QUOTED(token.tok_type) && sw_sql_dialect == 1)) {
            if (string_only)
                  SYNTAX_ERROR("<quoted string>");
            if (!MATCH(KW_COLON))
                  SYNTAX_ERROR("<colon> or <quoted string>");
      }
      return PAR_native_value(FALSE, FALSE);
}


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

static ACT act_alter(void)
{

      switch (token.tok_keyword) {

      case KW_DATABASE:
      case KW_SCHEMA:
            return act_alter_database();

      case KW_DOMAIN:
            return act_alter_domain();

      case KW_INDEX:
            ADVANCE_TOKEN;
            return act_alter_index();

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

      case KW_TABLE:
            ADVANCE_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)
{
      ACT action;
      GPRE_REQ request;
      DBB database;
      FIL file;
      SSHORT logdefined;

      request = MAKE_REQUEST(REQ_ddl);
      if (isc_databases && !isc_databases->dbb_next)
            request->req_database = isc_databases;
      else
            PAR_error("Can only alter database in context of single database");

      ADVANCE_TOKEN;

//  create action block 

      action = MAKE_ACTION(request, ACT_alter_database);
      action->act_whenever = gen_whenever();
      database = (DBB) ALLOC(DBB_LEN);
      database->dbb_grp_cmt_wait = -1;
      action->act_object = (REF) database;

      while (TRUE) {
            if (MATCH(KW_DROP)) {
                  if (MATCH(KW_LOG_FILE))
                        database->dbb_flags |= DBB_drop_log;
                  else if (MATCH(KW_CASCADE))
                        database->dbb_flags |= DBB_cascade;
#ifdef FLINT_CACHE
                  else if (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 (MATCH(KW_ADD)) {
                  if (MATCH(KW_FILE)) {
                        do {
                              file = define_file();
                              file->fil_next = database->dbb_files;
                              database->dbb_files = file;
                        }
                        while (MATCH(KW_FILE));
                  }
                  else if (MATCH(KW_LOG_FILE)) {
                        if (logdefined)
                              PAR_error("log redefinition");
                        logdefined = TRUE;
                        if (MATCH(KW_LEFT_PAREN)) {
                              while (TRUE) {
                                    file = define_log_file(FALSE);
                                    file->fil_next = database->dbb_logfiles;
                                    database->dbb_logfiles = file;
                                    if (!MATCH(KW_COMMA)) {
                                          EXP_match_paren();
                                          break;
                                    }
                              }

                              if (MATCH(KW_OVERFLOW))
                                    database->dbb_overflow = define_log_file(TRUE);
                              else
                                    PAR_error
                                          ("Overflow log specification required for this configuration");

                        }
                        else if (MATCH(KW_BASE_NAME)) {
                              database->dbb_flags |= DBB_log_serial;
                              database->dbb_logfiles = file = define_log_file(TRUE);
                        }
                        else
                              database->dbb_flags |= DBB_log_default;
                  }
#ifdef FLINT_CACHE
                  else if (MATCH(KW_CACHE))
                        database->dbb_cache_file = define_cache();
#endif /* FLINT_CACHE */
            }
            else if (MATCH(KW_SET)) {
                  while (TRUE) {
                        if (MATCH(KW_CHECK_POINT_LEN)) {
                              MATCH(KW_EQUALS);
                              database->dbb_chkptlen = EXP_ULONG_ordinal(TRUE);
                              MATCH(KW_COMMA);
                        }
                        else if (MATCH(KW_NUM_LOG_BUFS)) {
                              MATCH(KW_EQUALS);
                              database->dbb_numbufs = EXP_USHORT_ordinal(TRUE);
                              MATCH(KW_COMMA);
                        }
                        else if (MATCH(KW_LOG_BUF_SIZE)) {
                              MATCH(KW_EQUALS);
                              database->dbb_bufsize = EXP_USHORT_ordinal(TRUE);
                              MATCH(KW_COMMA);
                        }
                        else if (MATCH(KW_GROUP_COMMIT_WAIT)) {
                              MATCH(KW_EQUALS);
                              database->dbb_grp_cmt_wait = EXP_ULONG_ordinal(TRUE);
                              MATCH(KW_COMMA);
                        }
                        else
                              break;
                  }
            }
            else
                  break;
      }
      return action;
}


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

static ACT act_alter_domain(void)
{
      ACT action;
      GPRE_REQ request;
      GPRE_FLD field;
      CNSTRT *cnstrt_ptr, cnstrt;
      GPRE_NOD literal_node;

//  create request block 

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

//  create action block 

      action = MAKE_ACTION(request, ACT_alter_domain);

      ADVANCE_TOKEN;
      field = make_field(0);
      cnstrt_ptr = &field->fld_constraints;

//  Check if default value was specified 

      while (!END_OF_COMMAND) {
            if (MATCH(KW_SET)) {
                  if (token.tok_keyword == KW_DEFAULT) {
                        field->fld_default_source = CPR_start_text();
                        ADVANCE_TOKEN;
                  }
                  else
                        SYNTAX_ERROR("DEFAULT");

                  if (MATCH(KW_USER))
                        field->fld_default_value = MAKE_NODE(nod_user_name, 0);
                  else if (MATCH(KW_NULL))
                        field->fld_default_value = MAKE_NODE(nod_null, 0);
                  else {
                        if (MATCH(KW_MINUS)) {
                              if (token.tok_type != tok_number)
                                    SYNTAX_ERROR("<number>");

                              literal_node = EXP_literal();
                              field->fld_default_value = MSC_unary(nod_negate,
                                                                                     literal_node);
                        }
                        else if ((field->fld_default_value = EXP_literal()) == NULL)
                              SYNTAX_ERROR("<constant>");
                  }
                  CPR_end_text(field->fld_default_source);
            }
            else if (MATCH(KW_ADD)) {
                  MATCH(KW_CONSTRAINT);
                  if (token.tok_keyword == KW_CHECK) {
                        cnstrt = par_field_constraint(request, field, 0);
                        *cnstrt_ptr = cnstrt;
                        cnstrt_ptr = &cnstrt->cnstrt_next;
                  }
                  else
                        PAR_error("Invalid constraint.");
            }
            else if (MATCH(KW_DROP)) {
                  if (MATCH(KW_CONSTRAINT)) {
                        cnstrt = (CNSTRT) ALLOC(CNSTRT_LEN);
                        cnstrt->cnstrt_flags |= CNSTRT_delete;
                        *cnstrt_ptr = cnstrt;
                        cnstrt_ptr = &cnstrt->cnstrt_next;
                  }
                  else if (MATCH(KW_DEFAULT)) {
                        field->fld_default_value = MAKE_NODE(nod_erase, 0);
                  }
                  else
                        PAR_error("Invalid attribute for DROP");
            }
            else
                  SYNTAX_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)
{
      ACT action;
      GPRE_REQ request;
      IND index;
      SCHAR i_name[NAME_SIZE + 1];

//  create request block 

      request = MAKE_REQUEST(REQ_ddl);

      if (token.tok_length > NAME_SIZE)
            PAR_error("Index name too long");

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

      ADVANCE_TOKEN;

      index = make_index(request, i_name);

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

      action = MAKE_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)
{
      ACT action;
      GPRE_FLD field, *ptr;
      GPRE_REQ request;
      GPRE_REL relation;
      CNSTRT cnstrt, *cnstrt_ptr;
      GPRE_CTX context;

//  create request block 

      request = MAKE_REQUEST(REQ_ddl);

//  get table name and create relation block 

      relation = par_relation(request);

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

      request->req_contexts = context = MAKE_CONTEXT(request);
      context->ctx_relation = relation;

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

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

//  create action block 

      action = MAKE_ACTION(request, ACT_alter_table);
      action->act_whenever = gen_whenever();
      action->act_object = (REF) relation;

//  parse action list and create corresponding field blocks 

      ptr = &relation->rel_fields;
      cnstrt_ptr = &relation->rel_constraints;

      while (!END_OF_COMMAND) {
            if (MATCH(KW_ADD)) {
                  switch (token.tok_keyword) {
                  case KW_CONSTRAINT:
                  case KW_PRIMARY:
                  case KW_UNIQUE:
                  case KW_FOREIGN:
                  case KW_CHECK:
                        cnstrt = par_table_constraint(request, relation);
                        *cnstrt_ptr = cnstrt;
                        cnstrt_ptr = &cnstrt->cnstrt_next;
                        break;

                  default:
                        field = par_field(request, relation);
                        *ptr = field;
                        ptr = &field->fld_next;
                  }
            }
            else if (MATCH(KW_DROP)) {
                  if (token.tok_keyword == KW_CONSTRAINT) {
                        ADVANCE_TOKEN;
                        cnstrt = (CNSTRT) ALLOC(CNSTRT_LEN);
                        cnstrt->cnstrt_flags |= CNSTRT_delete;
                        cnstrt->cnstrt_name = (STR) ALLOC(NAME_SIZE + 1);
                        SQL_resolve_identifier("<constraint name>",
                                                         (TEXT *) cnstrt->cnstrt_name);
                        if (token.tok_length > NAME_SIZE)
                              PAR_error("Constraint name too long");
                        *cnstrt_ptr = cnstrt;
                        cnstrt_ptr = &cnstrt->cnstrt_next;
                        ADVANCE_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 (token.tok_keyword == KW_CASCADE) {
                              PAR_error("Unsupported construct CASCADE");
                              ADVANCE_TOKEN;
                        }
                        else if (token.tok_keyword == KW_RESTRICT) {
                              ADVANCE_TOKEN;
                        }
                  }
            }
            else
                  SYNTAX_ERROR("ADD or DROP");
            if (!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;
      GPRE_REQ request;
      SYM symbol;
      RDY ready;
      DBB dbb;
      BOOLEAN need_handle;
      USHORT default_buffers, buffers = 0;
      TEXT *user = NULL, *password = NULL, *sql_role = NULL;
      TEXT *lc_messages = NULL;

      action = MAKE_ACTION(0, ACT_ready);
      action->act_whenever = gen_whenever();
      need_handle = FALSE;
      default_buffers = 0;

      MATCH(KW_TO);

      if (!MATCH(KW_ALL) && !MATCH(KW_DEFAULT))
            while (TRUE) {
                  ready = (RDY) ALLOC(RDY_LEN);
                  ready->rdy_next = (RDY) action->act_object;
                  action->act_object = (REF) ready;

                  if (!(symbol = token.tok_symbol)
                        || symbol->sym_type != SYM_database) {
                        ready->rdy_filename = SQL_var_or_string(FALSE);
                        if (MATCH(KW_AS))
                              need_handle = TRUE;
                  }

                  if (!(symbol = token.tok_symbol)
                        || symbol->sym_type != SYM_database) {
                        if (!isc_databases || isc_databases->dbb_next || need_handle) {
                              need_handle = FALSE;
                              SYNTAX_ERROR("<database handle>");
                        }
                        ready->rdy_database = dup_dbb(isc_databases);
                  }

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

                  /* pick up the possible parameters, in any order */

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

                  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 (dbb->dbb_r_user || dbb->dbb_r_password || dbb->dbb_r_sql_role
                        || dbb->dbb_r_lc_messages || dbb->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 && (dbb->dbb_c_user || dbb->dbb_c_password ||
                                           dbb->dbb_c_sql_role ||
                                           dbb->dbb_c_lc_messages || dbb->dbb_c_lc_ctype))
                              request =
                              PAR_set_up_dpb_info(ready, action, default_buffers);

                  if (!MATCH(KW_COMMA))
                        break;
            }

      if (action->act_object)
            return action;

//  No explicit databases -- pick up all known 

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

      for (dbb = isc_databases; dbb; dbb = dbb->dbb_next)
            if (dbb->dbb_runtime || !(dbb->dbb_flags & DBB_sqlca)) {
                  ready = (RDY) ALLOC(RDY_LEN);
                  ready->rdy_next = (RDY) action->act_object;
                  action->act_object = (REF) ready;
                  ready->rdy_database = dup_dbb(dbb);
            }

      if (!action->act_object)
            PAR_error("no database available to CONNECT");
      else
            for (ready = (RDY) action->act_object; ready; ready = ready->rdy_next) {
                  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 = ready->rdy_database;
                  if (user || password || lc_messages || dbb->dbb_r_lc_ctype) {
                        dbb->dbb_r_user = user;
                        dbb->dbb_r_password = password;
                        dbb->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 && (dbb->dbb_c_user || dbb->dbb_c_password ||
                                           dbb->dbb_c_sql_role ||
                                           dbb->dbb_c_lc_ctype || dbb->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)
{
      BOOLEAN descending;
      BOOLEAN unique;

      if (MATCH(KW_DATABASE) || MATCH(KW_SCHEMA))
            return act_create_database();

      if (MATCH(KW_DOMAIN))
            return act_create_domain();

      if (MATCH(KW_GENERATOR))
            return act_create_generator();

      if (MATCH(KW_SHADOW))
            return act_create_shadow();

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

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

      if (MATCH(KW_TABLE))
            return act_create_table();

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

      if (MATCH(KW_VIEW))
            return (act_create_view());

      if (KEYWORD(KW_UNIQUE) || KEYWORD(KW_ASCENDING) ||
            KEYWORD(KW_DESCENDING) || KEYWORD(KW_INDEX)) {
            descending = FALSE;
            unique = FALSE;
            while (TRUE) {
                  if (MATCH(KW_UNIQUE))
                        unique = TRUE;
                  else if (MATCH(KW_ASCENDING))
                        descending = FALSE;
                  else if (MATCH(KW_DESCENDING))
                        descending = TRUE;
                  else if (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)
{
      ACT action;
      SYM symbol;
      DBB db;
      MDBB mdb;
      GPRE_REQ request;
      SCHAR *string;
      BOOLEAN dummy, extend_dpb;

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

      if (!isc_databases) {
            /* generate a dummy db */

            dummy = TRUE;
            isc_databases = (DBB) MSC_alloc_permanent(DBB_LEN);

            /* allocate symbol block */

            symbol = (SYM) MSC_alloc_permanent(SYM_LEN);

            /* make it the default database */

            symbol->sym_type = SYM_database;
            symbol->sym_object = (GPRE_CTX) isc_databases;
            symbol->sym_string = database_name;

            /* database block points to the symbol block */

            isc_databases->dbb_name = symbol;
            isc_databases->dbb_filename = NULL;
            isc_databases->dbb_c_lc_ctype = module_lc_ctype;
      }
      else
            dummy = FALSE;

//  get database name 

      if (QUOTED(token.tok_type)) {
            db = dup_dbb(isc_databases);
            db->dbb_filename = string = (TEXT *) ALLOC(token.tok_length + 1);
            COPY(token.tok_string, token.tok_length, string);
            if (!isc_databases->dbb_filename)
                  isc_databases->dbb_filename = string;
            ADVANCE_TOKEN;
      }
      else
            SYNTAX_ERROR("<quoted database name>");

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

//  request = MAKE_REQUEST (REQ_create_database); 
      request = MAKE_REQUEST(REQ_ddl);
      request->req_flags |= REQ_sql_database_dyn;
      request->req_database = db;

//  create action block 

      action = MAKE_ACTION(request, ACT_create_database);

      mdb = (MDBB) ALLOC(sizeof(struct 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 = MAKE_ACTION(0, ACT_database);
            action->act_rest->act_flags |= ACT_mark;
      }

//  Get optional specifications 

      extend_dpb = tail_database(ACT_create_database, db);

//  Create a request to generate dpb 

      ada_flags |= ADA_create_database;

      request = MAKE_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)
{
      ACT action;
      GPRE_REQ request;
      GPRE_FLD field;
      CNSTRT *cnstrt;
      int in_constraints;
      GPRE_NOD literal_node;

//  create request block 

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

//  create action block 

      action = MAKE_ACTION(request, ACT_create_domain);

      field = make_field(0);
      MATCH(KW_AS);
      SQL_par_field_dtype(request, field, FALSE);

//  Check if default value was specified 

      if (token.tok_keyword == KW_DEFAULT) {
            field->fld_default_source = CPR_start_text();
            ADVANCE_TOKEN;

            if (MATCH(KW_USER))
                  field->fld_default_value = MAKE_NODE(nod_user_name, 0);
            else if (MATCH(KW_NULL))
                  field->fld_default_value = MAKE_NODE(nod_null, 0);
            else {
                  if (MATCH(KW_MINUS)) {
                        if (token.tok_type != tok_number)
                              SYNTAX_ERROR("<number>");

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


//  Check for any column level constraints 

      cnstrt = &field->fld_constraints;
      in_constraints = TRUE;

      while (in_constraints) {
            switch (token.tok_keyword) {
            case KW_CONSTRAINT:
            case KW_CHECK:
            case KW_NOT:
                  *cnstrt = par_field_constraint(request, field, 0);
                  cnstrt = &(*cnstrt)->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)
{

      ACT action;
      TEXT *generator_name;
      GPRE_REQ request;


      generator_name = (TEXT *) ALLOC(NAME_SIZE + 1);
      SQL_resolve_identifier("<identifier>", generator_name);

      request = MAKE_REQUEST(REQ_ddl);
      if (isc_databases && !isc_databases->dbb_next)
            request->req_database = isc_databases;
      else
            PAR_error("Can only CREATE GENERATOR in context of single database");

      if (token.tok_length > NAME_SIZE)
            PAR_error("Generator name too long");


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

      ADVANCE_TOKEN;
      return action;
}


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

static ACT act_create_index( SSHORT dups, BOOLEAN descending)
{
      ACT action;
      GPRE_FLD *ptr;
      IND index;
      GPRE_REQ request;
      GPRE_REL relation;
      SCHAR i_name[NAME_SIZE + 1];

//  create request block 

      request = MAKE_REQUEST(REQ_ddl);

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

      if (token.tok_length > NAME_SIZE)
            PAR_error("Index name too long");


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

      ADVANCE_TOKEN;

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

      relation = par_relation(request);
      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 

      action = MAKE_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);
      ptr = &index->ind_fields;

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

      EXP_match_paren();

      return action;
}


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

static ACT act_create_shadow(void)
{

      ACT action;
      GPRE_REQ request;
      FIL file_list, file;
      SLONG shadow_number;
      USHORT file_flags = 0;
      SLONG length;
      TEXT err_string[1024];

      request = MAKE_REQUEST(REQ_ddl);
      if (isc_databases && !isc_databases->dbb_next)
            request->req_database = isc_databases;
      else
            PAR_error("Can only CREATE SHADOW in context of single database");

      action = MAKE_ACTION(request, ACT_create_shadow);
      action->act_whenever = gen_whenever();
      shadow_number = EXP_USHORT_ordinal((USHORT) TRUE);

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

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

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

      file_list = file = define_file();
      if (file->fil_start)
            PAR_error("Can not specify file start for first file");
      length = file->fil_length;
      file->fil_flags = file_flags;
      file->fil_shadow_number = (SSHORT) shadow_number;
      while (MATCH(KW_FILE)) {
            file = define_file();
            if (!length && !file->fil_start) {
                  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)
{
      ACT action;
      GPRE_FLD *ptr;
      GPRE_REQ request;
      GPRE_REL relation;
      CNSTRT *cnstrt;
      GPRE_CTX context;
      TEXT *string;

      request = MAKE_REQUEST(REQ_ddl);
      relation = par_relation(request);

      if (MATCH(KW_EXTERNAL)) {
            MATCH(KW_FILE);
            if (QUOTED(token.tok_type)) {
                  relation->rel_ext_file = string =
                        (TEXT *) ALLOC(token.tok_length + 1);
                  COPY(token.tok_string, token.tok_length, string);
                  ADVANCE_TOKEN;
            }
            else
                  SYNTAX_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 

      request->req_contexts = context = MAKE_CONTEXT(request);
      context->ctx_relation = relation;

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

//  create action block 

      action = MAKE_ACTION(request, ACT_create_table);
      action->act_whenever = gen_whenever();
      action->act_object = (REF) relation;

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

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

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

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

      return action;
}


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

static ACT act_create_view(void)
{
      ACT action;
      GPRE_FLD *ptr;
      GPRE_REQ request;
      GPRE_REL relation;
      RSE select;

      request = MAKE_REQUEST(REQ_ddl);
      relation = par_relation(request);

//  create action block 

      action = MAKE_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 (MATCH(KW_LEFT_PAREN)) {
            ptr = &relation->rel_fields;
            for (;;) {
                  *ptr = make_field(relation);
                  ptr = &(*ptr)->fld_next;
                  if (MATCH(KW_RIGHT_PAREN))
                        break;
                  MATCH(KW_COMMA);
            }
      }

//  skip 'AS SELECT' 

      if (!MATCH(KW_AS))
            SYNTAX_ERROR("AS");

      relation->rel_view_text = CPR_start_text();
      if (!MATCH(KW_SELECT))
            SYNTAX_ERROR("SELECT");

//  reserve context variable 0 for view 

      request->req_internal++;

//  parse the view SELECT 

      relation->rel_view_rse = select = SQE_select(request, TRUE);
      EXP_rse_cleanup(select);

      if (MATCH(KW_WITH)) {
            if (!MATCH(KW_CHECK))
                  SYNTAX_ERROR("CHECK");
            if (!MATCH(KW_OPTION))
                  SYNTAX_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)
{
      ACT action;
      SYM symbol;

      if (!MATCH(KW_DECLARE))
            SYNTAX_ERROR("DECLARE SECTION");

      if (!MATCH(KW_SECTION))
            SYNTAX_ERROR("SECTION");

      MATCH(KW_SEMI_COLON);

      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = type;

      if (type == ACT_b_declare)
            cur_routine = action;

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

            isc_databases = (DBB) MSC_alloc_permanent(DBB_LEN);

            /* allocate symbol block */

            symbol = (SYM) MSC_alloc_permanent(SYM_LEN);

            /* make it a database, specifically this one */

            symbol->sym_type = SYM_database;
            symbol->sym_object = (GPRE_CTX) isc_databases;
            symbol->sym_string = database_name;

            /* database block points to the symbol block */

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

            MET_load_hash_table(isc_databases);
      }

      HSH_insert(isc_databases->dbb_name);

      if (MATCH(KW_SQL)) {
            if (!MATCH(KW_NAMES))
                  SYNTAX_ERROR("NAMES ARE");
            if (!MATCH(KW_ARE))
                  SYNTAX_ERROR("NAMES ARE");
            if (!(symbol = MSC_find_symbol(token.tok_symbol, SYM_charset)))
                  PAR_error("The named CHARACTER SET was not found");
            if (module_lc_ctype && !strcmp(module_lc_ctype, symbol->sym_string))
                  PAR_error("Duplicate specification of module CHARACTER SET.");
            module_lc_ctype = symbol->sym_string;
            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;
      GPRE_REQ request;
      ACT action;
      SYM symbol;
      DYN statement;
      TEXT t_str[132];
      BOOLEAN delimited = FALSE;
#ifdef SCROLLABLE_CURSORS
      BOOLEAN scroll = FALSE;
#endif

      db = NULL;

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

            db = (DBB) token.tok_symbol->sym_object;
            ADVANCE_TOKEN;
            if (!MATCH(KW_DOT))
                  SYNTAX_ERROR(". (period)");
            symbol = PAR_symbol(SYM_dummy);
            if (token.tok_keyword != KW_TABLE)
                  SYNTAX_ERROR("TABLE");
            return (act_declare_table(symbol, db));
      }

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

      case KW_EXTERNAL:
            ADVANCE_TOKEN;
            if (MATCH(KW_FUNCTION))
                  return (act_declare_udf());
            else
                  SYNTAX_ERROR("FUNCTION");
            break;
      }

      SQL_resolve_identifier("<Cursor Name>", t_str);
      if (token.tok_type == tok_dblquoted)
            delimited = TRUE;
      else {
            SYM symb;
            if ((symb = HSH_lookup2(token.tok_string)) &&
                  (symb->sym_type == SYM_cursor ||
                   symb->sym_type == SYM_delimited_cursor)) {
                  char s[64];
                  sprintf(s, "symbol %s is already in use", t_str);
                  PAR_error(s);
            }
      }
      symbol = PAR_symbol(SYM_cursor);

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

#ifdef SCROLLABLE_CURSORS
      case KW_SCROLL:
            ADVANCE_TOKEN;
            scroll = TRUE;
            if (token.tok_keyword != KW_CURSOR)
                  SYNTAX_ERROR("CURSOR");
#endif
      case KW_CURSOR:
            ADVANCE_TOKEN;
            if (!MATCH(KW_FOR))
                  SYNTAX_ERROR("FOR");
            if (MATCH(KW_SELECT)) {
                  request = MAKE_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 = MAKE_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 (MATCH(KW_FOR)) {
                        if (!MATCH(KW_UPDATE))
                              SYNTAX_ERROR("UPDATE");
                        if (!MATCH(KW_OF))
                              SYNTAX_ERROR("OF");

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

                        do
                              CPR_token();
                        while (MATCH(KW_COMMA));
                  }
                  symbol->sym_type = SYM_dyn_cursor;
                  statement->dyn_cursor_name = symbol;
                  action = (ACT) 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 (MATCH(KW_COMMA));
            if (MATCH(KW_STATEMENT)) {
                  action = (ACT) ALLOC(ACT_LEN);
                  action->act_type = ACT_dyn_statement;
                  return action;
            }
            SYNTAX_ERROR("CURSOR, STATEMENT or TABLE");
      }
      return NULL;                        /* silence compiler */
}


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

static ACT act_declare_filter(void)
{
      ACT action;
      GPRE_REQ request;
      FLTR filter;
      SLONG input_type, output_type;

      request = MAKE_REQUEST(REQ_ddl);

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

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

      ADVANCE_TOKEN;

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

      if (MATCH(KW_INPUT_TYPE))
            input_type = EXP_SSHORT_ordinal((USHORT) TRUE);
      else
            SYNTAX_ERROR("INPUT_TYPE");

      if (MATCH(KW_OUTPUT_TYPE))
            output_type = EXP_SSHORT_ordinal((USHORT) TRUE);
      else
            SYNTAX_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 (MATCH(KW_ENTRY_POINT))
            filter->fltr_entry_point = extract_string(TRUE);
      else
            SYNTAX_ERROR("ENTRY_POINT");

      if (MATCH(KW_MODULE_NAME))
            filter->fltr_module_name = extract_string(TRUE);
      else
            SYNTAX_ERROR("MODULE_NAME");

      return action;
}


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

static ACT act_declare_table( SYM symbol, DBB db)
{
      ACT action;
      GPRE_FLD field, dbkey, *ptr;
      GPRE_REL relation, tmp_relation;
      SYM old_symbol, tmp_symbol;
      USHORT count;
      GPRE_REQ request;

//  create a local request block 

      request = (GPRE_REQ) ALLOC(REQ_LEN);
      request->req_type = REQ_ddl;

//  create relation block 

      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;
      relation->rel_dbkey = dbkey =
            MET_make_field("rdb$db_key", dtype_text, 8, FALSE);
      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 

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

      while (old_symbol)
            if (old_symbol->sym_type == SYM_relation) {
                  tmp_relation = (GPRE_REL) old_symbol->sym_object;
                  if (tmp_relation->rel_meta)
                        PAR_error("Multiple DECLARE TABLE statements for table");
                  tmp_symbol = old_symbol->sym_homonym;
                  HSH_remove(old_symbol);
                  for (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 

      action = (ACT) 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 

      ADVANCE_TOKEN;
      EXP_left_paren(0);
      count = 0;
      ptr = &relation->rel_fields;

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

            if (!MATCH(KW_COMMA))
                  break;
      }

      EXP_match_paren();

      return action;
}


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

static ACT act_declare_udf(void)
{
      ACT action;
      GPRE_REQ request;
      DECL_UDF udf;
      GPRE_FLD *ptr, field;
      SLONG return_parameter;

      request = MAKE_REQUEST(REQ_ddl);

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

      udf = (DECL_UDF) ALLOC(DECL_UDF_LEN);
      udf->decl_udf_name = (TEXT *) ALLOC(NAME_SIZE + 1);
      SQL_resolve_identifier("<identifier>", udf->decl_udf_name);
      if (token.tok_length > NAME_SIZE)
            PAR_error("external function name too long");
      ADVANCE_TOKEN;

//  create action block 
      action = MAKE_ACTION(request, ACT_declare_udf);
      action->act_whenever = gen_whenever();
      action->act_object = (REF) udf;

      ptr = &udf->decl_udf_arg_list;
      while (TRUE) {
            if (MATCH(KW_RETURNS)) {
                  if (MATCH(KW_PARAMETER)) {
                        return_parameter = EXP_pos_USHORT_ordinal((USHORT) TRUE);
                        if (return_parameter > 10)
                              PAR_error("return parameter not in range");
                        assert(return_parameter <= MAX_SSHORT);
                        udf->decl_udf_return_parameter = (SSHORT) return_parameter;
                  }
                  else {
                        field = (GPRE_FLD) ALLOC(FLD_LEN);
                        field->fld_flags |= (FLD_meta | FLD_meta_cstring);
                        SQL_par_field_dtype(request, field, TRUE);
                        SQL_adjust_field_dtype(field);
                        udf->decl_udf_return_type = field;
                        MATCH(KW_BY);
                        if (MATCH(KW_VALUE))
                              udf->decl_udf_return_mode = FUN_value;
                        else
                              udf->decl_udf_return_mode = FUN_reference;
                  }
                  break;
            }
            else {
                  field = (GPRE_FLD) 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);
                  MATCH(KW_COMMA);
            }
      }

      if (MATCH(KW_ENTRY_POINT))
            udf->decl_udf_entry_point = extract_string(TRUE);
      else
            SYNTAX_ERROR("ENTRY_POINT");

      if (MATCH(KW_MODULE_NAME))
            udf->decl_udf_module_name = extract_string(TRUE);
      else
            SYNTAX_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)
{
      ACT action;
      GPRE_REQ request;
      GPRE_REL relation;
      RSE rse;
      GPRE_CTX context;
      UPD update;
      SYM alias;
      TEXT *transaction, r_name[NAME_SIZE + 1], db_name[NAME_SIZE + 1],
            owner_name[NAME_SIZE + 1], *str_1, *str_2;
      SSHORT where, i, trans_nm_len;
      SSHORT hsh_rm = 0;

      par_options(&transaction);

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

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

      SQL_relation_name(r_name, db_name, owner_name);

//  Parse the optional alias (context variable) 

      alias = (token.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 

      request = MAKE_REQUEST(REQ_mass_update);
      update = (UPD) ALLOC(UPD_LEN);

      if ((where = MATCH(KW_WITH)) && MATCH(KW_CURRENT)) {
            if (!MATCH(KW_OF))
                  SYNTAX_ERROR("OF <cursor>");
            requests = request->req_next;
            cur_routine->act_object = (REF) request->req_routine;
            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 */
                        trans_nm_len = strlen(request->req_trans);
                        str_2 = transaction = (SCHAR *) ALLOC(trans_nm_len + 1);
                        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);
            rse = request->req_rse;
            for (i = 0; i < rse->rse_count; i++) {
                  context = rse->rse_context[i];
                  if (context->ctx_relation == relation)
                        break;
            }
            if (i == rse->rse_count)
                  PAR_error("table not in request");
            update->upd_request = request;
            update->upd_source = context;
            action = MAKE_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.  

      relation = SQL_relation(request, r_name, db_name, owner_name, TRUE);

      request->req_rse = rse = (RSE) ALLOC(RSE_LEN(1));
      rse->rse_count = 1;
      rse->rse_context[0] = context = MAKE_CONTEXT(request);
      context->ctx_relation = relation;

      if (alias && !token.tok_symbol) {
            alias->sym_type = SYM_context;
            alias->sym_object = context;
            context->ctx_symbol = alias;
            token.tok_symbol = alias;
            HSH_insert(alias);
            hsh_rm = 1;
      }

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

      request->req_node = MAKE_NODE(nod_erase, 0);
      action = MAKE_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)
{
      DYN statement;
      ACT action;
      BOOLEAN in_sqlda;

      if (MATCH(KW_INPUT))
            in_sqlda = TRUE;
      else {
            MATCH(KW_OUTPUT);
            in_sqlda = FALSE;
      }

      statement = par_statement();

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

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

      statement->dyn_sqlda = PAR_native_value(FALSE, FALSE);
      action = (ACT) 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;
      SYM symbol;
      RDY ready;
      USHORT all;

      action = MAKE_ACTION(0, ACT_disconnect);
      action->act_whenever = gen_whenever();
      all = MATCH(KW_ALL) || MATCH(KW_DEFAULT);

      if (!all) {
            if (MATCH(KW_CURRENT))
                  PAR_error("DISCONNECT CURRENT not supported");
            if (!
                  ((symbol = token.tok_symbol)
                   && (symbol->sym_type ==
                         SYM_database)))
SYNTAX_ERROR("ALL, DEFAULT, or <database handle>");
            while (TRUE) {
                  if ((symbol = token.tok_symbol)
                        && (symbol->sym_type == SYM_database)) {
                        ready = (RDY) ALLOC(RDY_LEN);
                        ready->rdy_next = (RDY) action->act_object;
                        action->act_object = (REF) ready;
                        ready->rdy_database = (DBB) symbol->sym_object;
                        ADVANCE_TOKEN;
                  }
                  else
                        SYNTAX_ERROR("<database handle>");
                  if (!MATCH(KW_COMMA))
                        break;
            }
      }

      if (sw_language == lang_ada)
            MATCH(KW_SEMI_COLON);
      return action;
}


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

static ACT act_drop(void)
{
      ACT action;
      DBB db;
      IND index;
      GPRE_REQ request;
      GPRE_REL relation;
      SYM symbol;
      SCHAR *db_string;
      TEXT *str;
      SLONG num;

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

            request = MAKE_REQUEST(REQ_ddl);
            ADVANCE_TOKEN;
            if (!QUOTED(token.tok_type))
                  SYNTAX_ERROR("<quoted database name>");
            db = (DBB) ALLOC(DBB_LEN);
            db->dbb_filename = db_string = (TEXT *) ALLOC(token.tok_length + 1);
            COPY(token.tok_string, token.tok_length, db_string);
            symbol = PAR_symbol(SYM_dummy);
            db->dbb_name = symbol;
            symbol->sym_type = SYM_database;
            symbol->sym_object = (GPRE_CTX) db;
            action = MAKE_ACTION(request, ACT_drop_database);
            action->act_whenever = gen_whenever();
            action->act_object = (REF) db;
            ADVANCE_TOKEN;
            return action;

      case KW_DOMAIN:
            request = MAKE_REQUEST(REQ_ddl);
            if (isc_databases && !isc_databases->dbb_next)
                  request->req_database = isc_databases;
            else
                  PAR_error("Can only DROP DOMAIN in context of single database");
            ADVANCE_TOKEN;
            str = (TEXT *) ALLOC(NAME_SIZE + 1);
            SQL_resolve_identifier("<identifier>", str);
            action = MAKE_ACTION(request, ACT_drop_domain);
            action->act_whenever = gen_whenever();
            action->act_object = (REF) str;
            ADVANCE_TOKEN;
            return action;

      case KW_FILTER:
            request = MAKE_REQUEST(REQ_ddl);
            if (isc_databases && !isc_databases->dbb_next)
                  request->req_database = isc_databases;
            else
                  PAR_error("Can only DROP FILTER in context of single database");
            ADVANCE_TOKEN;
            str = (TEXT *) ALLOC(NAME_SIZE + 1);
            SQL_resolve_identifier("<identifier>", str);
            action = MAKE_ACTION(request, ACT_drop_filter);
            action->act_whenever = gen_whenever();
            action->act_object = (REF) str;
            ADVANCE_TOKEN;
            return action;

      case KW_EXTERNAL:

            ADVANCE_TOKEN;
            if (!MATCH(KW_FUNCTION))
                  SYNTAX_ERROR("FUNCTION");
            request = MAKE_REQUEST(REQ_ddl);
            if (isc_databases && !isc_databases->dbb_next)
                  request->req_database = isc_databases;
            else
                  PAR_error
                        ("Can only DROP EXTERNAL FUNCTION in context of a single database");

            str = (TEXT *) ALLOC(NAME_SIZE + 1);
            SQL_resolve_identifier("<identifier>", str);
            action = MAKE_ACTION(request, ACT_drop_udf);
            action->act_whenever = gen_whenever();
            action->act_object = (REF) str;
            ADVANCE_TOKEN;
            return action;

      case KW_INDEX:
            request = MAKE_REQUEST(REQ_ddl);
            ADVANCE_TOKEN;
            str = (TEXT *) ALLOC(NAME_SIZE + 1);
            SQL_resolve_identifier("<identifier>", str);
            index = make_index(request, token.tok_string);
            action = MAKE_ACTION(request, ACT_drop_index);
            action->act_whenever = gen_whenever();
            action->act_object = (REF) index;
            ADVANCE_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 = MAKE_REQUEST(REQ_ddl);
            if (isc_databases && !isc_databases->dbb_next)
                  request->req_database = isc_databases;
            else
                  PAR_error("Can only DROP SHADOW in context of a single database");
            ADVANCE_TOKEN;
            action = MAKE_ACTION(request, ACT_drop_shadow);
            action->act_whenever = gen_whenever();
            num = EXP_USHORT_ordinal(TRUE);
            if (!RANGE_POSITIVE_SHORT_INTEGER(num))
                  PAR_error("Shadow number out of range");
            action->act_object = (REF) num;
            return action;

      case KW_TABLE:
            request = MAKE_REQUEST(REQ_ddl);
            ADVANCE_TOKEN;
            relation = par_relation(request);
            action = MAKE_ACTION(request, ACT_drop_table);
            action->act_whenever = gen_whenever();
            action->act_object = (REF) relation;
            return action;

      case KW_VIEW:
            request = MAKE_REQUEST(REQ_ddl);
            ADVANCE_TOKEN;
            relation = par_relation(request);
            action = MAKE_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;

      if (MATCH(KW_INIT))
            action = PAR_event_init((USHORT) TRUE);
      else if (MATCH(KW_WAIT))
            action = PAR_event_wait((USHORT) TRUE);
      else
            SYNTAX_ERROR("INIT or WAIT");
      action->act_whenever = gen_whenever();
      return action;
}


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

static ACT act_execute(void)
{
      DYN statement;
      ACT action;
      TEXT *transaction, s[ERROR_LENGTH];

      if (MATCH(KW_PROCEDURE))
            return act_procedure();

//  EXECUTE IMMEDIATE is a different sort of duck 

      if (MATCH(KW_IMMEDIATE)) {
            if (isc_databases && isc_databases->dbb_next) {
                  sprintf(s,
                              "Executing dynamic SQL statement in context of database %s",
                              isc_databases->dbb_name->sym_string);
                  CPR_warn(s);
            }
            statement = (DYN) ALLOC(DYN_LEN);
            par_options(&statement->dyn_trans);

            switch (sw_sql_dialect) {
            case 1:
                  if ((!QUOTED(token.tok_type)) && (!MATCH(KW_COLON)))
                        SYNTAX_ERROR(": <string expression>");
                  break;

            default:
                  if ((!SINGLE_QUOTED(token.tok_type)) && (!MATCH(KW_COLON)))
                        SYNTAX_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 = isc_databases;
            action = (ACT) 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 

      par_options(&transaction);
      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);

      action = (ACT) 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)
{
      GPRE_REQ request;
      ACT action;
      RSE select;
      DYN statement, cursor;
#ifdef SCROLLABLE_CURSORS
      USHORT direction;
      REF reference;
      VAL value;
      TEXT *direction_string, *offset_string, *string;
      GPRE_NOD offset_node = NULL;
#endif

//  Handle dynamic SQL statement, if appropriate 

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

#pragma FB_COMPILER_MESSAGE("Fix! Wrong function ptr type!")
//
// Please search for "reinterpret_cast<pfn_SQE_list_cb>",
// there are more in this file.
//
                  if (MATCH(KW_DESCRIPTOR))
                        statement->dyn_sqlda = PAR_native_value(FALSE, FALSE);
                  else
                        statement->dyn_using =
                              (GPRE_NOD) SQE_list(reinterpret_cast < pfn_SQE_list_cb >
                                                   (SQE_variable), 0, FALSE);
                  if (statement->dyn_using)
                        PAR_error("Using host-variable list not supported.");
            }
            action = (ACT) 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 

      direction = blr_forward;

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

      MATCH(KW_FROM);
#endif

      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 */

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

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

            value->val_value = string = (TEXT *) ALLOC(2);
            COPY(direction_string, 1, string);

            /* create a reference to the offset variable or literal */

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

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

            if (offset_node)
                  value->val_value = ((REF) offset_node->nod_arg[0])->ref_value;
            else {
                  offset_string = "1";
                  value->val_value = string = (TEXT *) ALLOC(2);
                  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.");

      action = MAKE_ACTION(request, ACT_fetch);
      action->act_whenever = gen_whenever();

      if (request->req_flags & REQ_sql_blob_open) {
            if (!MATCH(KW_INTO))
                  SYNTAX_ERROR("INTO");
            action->act_object = SQE_variable(0, FALSE);
            action->act_type = ACT_get_segment;
      }
      else if (MATCH(KW_INTO)) {
            action->act_object =
                  (REF) SQE_list(reinterpret_cast < pfn_SQE_list_cb >
                                       (SQE_variable), request, FALSE);
            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;
      ACT action;
      USN user, usernames;
      PRV priv_block, last_priv_block;
      GPRE_REL relation;
      GPRE_PRC procedure;
      USHORT user_dyn;
      SCHAR col_name[NAME_SIZE + 1], r_name[NAME_SIZE + 1],
            db_name[NAME_SIZE + 1], owner_name[NAME_SIZE + 1];
      LLS *fields;
      STR field_name, relation_name;
      BOOLEAN first;
      BOOLEAN grant_option_legal = TRUE;
      BOOLEAN execute_priv = FALSE;
      SCHAR s[ERROR_LENGTH];

      request = MAKE_REQUEST(REQ_ddl);

      priv_block = MAKE_PRIVILEGE_BLOCK();
      fields = &priv_block->prv_fields;

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

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

      if (MATCH(KW_ALL)) {
            MATCH(KW_PRIVILEGES);   /*  Keyword 'privileges' is optional  */
            priv_block->prv_privileges = PRV_all;
      }
      else if (MATCH(KW_EXECUTE)) {
            priv_block->prv_privileges |= PRV_execute;
            execute_priv = TRUE;
      }
      else
            while (TRUE) {
                  if (MATCH(KW_SELECT))
                        priv_block->prv_privileges |= PRV_select;
                  else if (MATCH(KW_INSERT))
                        priv_block->prv_privileges |= PRV_insert;
                  else if (MATCH(KW_DELETE))
                        priv_block->prv_privileges |= PRV_delete;
                  else if (MATCH(KW_UPDATE)) {
                        priv_block->prv_privileges |= PRV_update;
                        if (MATCH(KW_LEFT_PAREN)) {
                              do {
                                    SQL_resolve_identifier("<column name>", col_name);
                                    field_name = (STR) MAKE_STRING(col_name);
                                    PUSH((GPRE_NOD) field_name, fields);
                                    fields = &(*fields)->lls_next;
                                    CPR_token();
                              } while (MATCH(KW_COMMA));

                              if (!MATCH(KW_RIGHT_PAREN))
                                    SYNTAX_ERROR("<right parenthesis>");
                        }
                  }

                  if (!MATCH(KW_COMMA))
                        break;
            }

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

      if (execute_priv) {
            if (!MATCH(KW_PROCEDURE))
                  SYNTAX_ERROR("PROCEDURE");
            SQL_relation_name(r_name, db_name, owner_name);
            procedure = SQL_procedure(request, r_name, db_name, owner_name, TRUE);
            relation_name = (STR) MAKE_STRING(r_name);
            priv_block->prv_relation = relation_name;
            priv_block->prv_object_dyn = gds_dyn_prc_name;
      }
      else {
            MATCH(KW_TABLE);        /* filler word */
            SQL_relation_name(r_name, db_name, owner_name);
            relation = SQL_relation(request, r_name, db_name, owner_name, TRUE);
            relation_name = (STR) MAKE_STRING(r_name);
            priv_block->prv_relation = relation_name;
            priv_block->prv_object_dyn = gds_dyn_rel_name;
      }

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

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

      usernames = 0;
      user = 0;
      while (TRUE) {
            if (MATCH(KW_PROCEDURE)) {
                  SQL_relation_name(r_name, db_name, owner_name);
                  procedure =
                        SQL_procedure(request, r_name, db_name, owner_name, TRUE);
                  user_dyn = gds_dyn_grant_proc;
                  grant_option_legal = FALSE;
            }
            else if (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 = gds_dyn_grant_trig;
                  grant_option_legal = FALSE;
            }
            else if (MATCH(KW_VIEW)) {
                  SQL_relation_name(r_name, db_name, owner_name);
                  if (!MET_get_view_relation
                        (request, r_name, (char *) relation_name, 0)) {
                        sprintf(s, "VIEW %s not defined on table %s", r_name,
                                    relation_name->str_string);
                        PAR_error(s);
                  }
                  user_dyn = gds_dyn_grant_view;
                  grant_option_legal = FALSE;
            }
            else if (MATCH(KW_PUBLIC)) {
                  strcpy(r_name, "PUBLIC");
                  user_dyn = gds_dyn_grant_user;
            }
            else {
                  MATCH(KW_USER);
                  if (token.tok_type != tok_ident)
                        SYNTAX_ERROR("<user name identifier>");
                  else
                        to_upcase(token.tok_string, r_name);
                  user_dyn = gds_dyn_grant_user;
                  CPR_token();
            }

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

            if (!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 (MATCH(KW_WITH)) {
                  if (!MATCH(KW_GRANT))
                        SYNTAX_ERROR("GRANT");
                  if (!MATCH(KW_OPTION))
                        SYNTAX_ERROR("OPTION");
                  priv_block->prv_privileges |= PRV_grant_option;
            }

//  create action block 

      action = MAKE_ACTION(request, type);
      action->act_next = 0;

      last_priv_block = priv_block;
      first = TRUE;

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

            priv_block = MAKE_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)
{
      ACT action;
      SYM symbol;

      ADVANCE_TOKEN;
      MATCH(KW_SEMI_COLON);

      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = ACT_b_declare;
      cur_routine = action;

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

            isc_databases = (DBB) MSC_alloc_permanent(DBB_LEN);

            /* allocate symbol block */

            symbol = (SYM) MSC_alloc_permanent(SYM_LEN);

            /* make it a database, specifically this one */

            symbol->sym_type = SYM_database;
            symbol->sym_object = (GPRE_CTX) isc_databases;
            symbol->sym_string = database_name;

            /* database block points to the symbol block */

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

      HSH_insert(isc_databases->dbb_name);

      return action;
}


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

static ACT act_insert(void)
{
      GPRE_REQ request;
      ACT action;
      GPRE_CTX context;
      LLS fields, values;
      RSE select;
      GPRE_NOD node, store, assignment, list, select_list, *ptr, *ptr2;
      TEXT *transaction;
      int count, count2, i;

      par_options(&transaction);

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

      if (!MATCH(KW_INTO))
            SYNTAX_ERROR("INTO");

      request = MAKE_REQUEST(REQ_insert);
      request->req_trans = transaction;
      context = SQE_context(request);
      count = count2 = 0;
      fields = values = NULL;

//  Pick up a field list 

      if (!MATCH(KW_LEFT_PAREN)) {
            list = MET_fields(context);
            count = list->nod_count;
            for (i = 0; i < count; i++)
                  PUSH(list->nod_arg[i], &fields);
      }
      else {
            do {
                  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 == sw_sql_dialect) &&
                        (nod_field == node->nod_type)) {
                        USHORT field_dtype;

                        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)) {
                              dialect1_bad_type(field_dtype);
                        }
                  }

                  PUSH(node, &fields);
                  count++;
            } while (MATCH(KW_COMMA));

            EXP_match_paren();
      }

      if (MATCH(KW_VALUES)) {
            /* Now pick up a value list */

            EXP_left_paren(0);
            for (;;) {
                  if (MATCH(KW_NULL))
                        PUSH(MAKE_NODE(nod_null, 0), &values);
                  else
                        PUSH(SQE_value(request, FALSE, 0, 0), &values);
                  count2++;
                  if (!(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");

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

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

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

      if (!MATCH(KW_SELECT))
            SYNTAX_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;
      request->req_rse = select = SQE_select(request, FALSE);
      context->ctx_next = request->req_contexts;
      request->req_contexts = context;

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

      select_list = select->rse_fields;

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

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

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

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

      action = MAKE_ACTION(request, ACT_loop);
      action->act_whenever = gen_whenever();

      return action;
}


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

static ACT act_insert_blob( TEXT * transaction)
{
      GPRE_REQ request;
      ACT action;
      DYN statement, cursor;

//  Handle dynamic SQL statement, if appropriate 

      if (cursor = par_dynamic_cursor()) {
            statement = (DYN) 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.");
            action = (ACT) 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 

      request = par_cursor(NULL);
      if (request->req_flags & REQ_sql_blob_open)
            PAR_error("Inserting into a blob being opened is not allowed.");
      action = MAKE_ACTION(request, ACT_put_segment);
      action->act_whenever = gen_whenever();

      if (!MATCH(KW_VALUES))
            SYNTAX_ERROR("VALUES");

      EXP_left_paren(0);
      action->act_object = SQE_variable(0, FALSE);
      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)
{
      ACT action;
      GPRE_REQ request;
      DYN statement, cursor;
      SYM symbol;
      OPN open;
      REF using_;
      TEXT *transaction;

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

//  Handle dynamic SQL statement, if appropriate 

      if (cursor = par_dynamic_cursor()) {
            statement = (DYN) ALLOC(DYN_LEN);
            statement->dyn_statement_name = cursor->dyn_statement_name;
            statement->dyn_cursor_name = cursor->dyn_cursor_name;
            action = (ACT) 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 

      request = par_cursor(&symbol);

      action = MAKE_ACTION(request, type);
      if (type == ACT_open) {
            open = (OPN) 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 (!MATCH(KW_USING))
                              SYNTAX_ERROR("USING");
                  }
                  else if (!MATCH(KW_INTO))
                        SYNTAX_ERROR("INTO");
                  open->opn_using = using_ = SQE_variable(0, FALSE);
                  using_->ref_next = request->req_blobs->blb_reference;
                  request->req_blobs->blb_reference = using_;
                  using_->ref_context = request->req_contexts;
                  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, SYM symbol)
{
      TOK f_token;
      SCHAR r_name[NAME_SIZE + 1], db_name[NAME_SIZE + 1],
            owner_name[NAME_SIZE + 1];
      GPRE_REQ request;
      GPRE_REL relation;
      GPRE_FLD field;
      REF reference;
      BLB blob;
      GPRE_CTX context;
      SCHAR s[128];
      ACT action;

      if (!MATCH(KW_BLOB))
            SYNTAX_ERROR("BLOB");

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

      f_token = (TOK) ALLOC(TOK_LEN);
      f_token->tok_length = token.tok_length;

      SQL_resolve_identifier("<column_name>", f_token->tok_string);
      CPR_token();

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

      request = MAKE_REQUEST(REQ_cursor);
      request->req_flags =
            (act_op == ACT_blob_open) ? REQ_sql_blob_open : REQ_sql_blob_create;

      SQL_relation_name(r_name, db_name, owner_name);
      relation = SQL_relation(request, r_name, db_name, owner_name, TRUE);

      if (!(field = MET_field(relation, f_token->tok_string))) {
            sprintf(s, "column \"%s\" not in context", f_token->tok_string);
            PAR_error(s);
      }

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

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

      reference = MAKE_REFERENCE(0);
      reference->ref_field = field;

      context = MAKE_CONTEXT(request);
      context->ctx_relation = relation;

      blob = (BLB) 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 (MATCH(KW_FILTER)) {
            if (MATCH(KW_FROM)) {
                  blob->blb_const_from_type =
                        PAR_blob_subtype(request->req_database);
                  if (token.tok_keyword == KW_CHAR)
                        if (blob->blb_const_from_type == 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 == 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 == BLOB_text)
                        if (act_op == ACT_blob_create)
                              blob->blb_from_charset = CS_dynamic;
                        else
                              blob->blb_from_charset = field->fld_charset_id;
            }
            if (!MATCH(KW_TO))
                  SYNTAX_ERROR("TO");
            blob->blb_const_to_type = PAR_blob_subtype(request->req_database);
            if (token.tok_keyword == KW_CHAR)
                  if (blob->blb_const_to_type == 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 == 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 == BLOB_text
                  && (field->fld_charset_id != CS_NONE)) {
                  blob->blb_const_from_type = BLOB_text;
                  blob->blb_const_to_type = 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 (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;

      action = MAKE_ACTION(request, ACT_cursor);
      action->act_object = (REF) blob;

      return action;
}


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

static ACT act_prepare(void)
{
      DYN statement;
      ACT action;
      TEXT s[ERROR_LENGTH];
      TEXT *transaction;

      if (isc_databases && isc_databases->dbb_next) {
            sprintf(s,
                        "Executing dynamic SQL statement in context of database %s",
                        isc_databases->dbb_name->sym_string);
            CPR_warn(s);
      }

      par_options(&transaction);
      statement = par_statement();
      statement->dyn_database = isc_databases;
      statement->dyn_trans = transaction;

      if (MATCH(KW_INTO)) {
            if (MATCH(KW_SQL) && !MATCH(KW_DESCRIPTOR))
                  SYNTAX_ERROR("INTO SQL DESCRIPTOR sqlda");
            statement->dyn_sqlda = PAR_native_value(FALSE, FALSE);
      }

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

      switch (sw_sql_dialect) {
      case 1:
            if ((!QUOTED(token.tok_type)) && (!MATCH(KW_COLON)))
                  SYNTAX_ERROR(": <string expression>");
            break;

      default:
            if ((!SINGLE_QUOTED(token.tok_type)) && (!MATCH(KW_COLON)))
                  SYNTAX_ERROR(": <string expression>");
            break;
      }

      statement->dyn_string = PAR_native_value(FALSE, FALSE);
      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = ACT_dyn_prepare;
      action->act_object = (REF) statement;
      action->act_whenever = gen_whenever();
      sw_dsql = TRUE;

      return action;
}


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

static ACT act_procedure(void)
{
      GPRE_PRC procedure;
      GPRE_REQ request;
      ACT action;
      REF reference, *ref_ptr;
      GPRE_FLD field;
      SSHORT inputs, outputs;
      LLS values;
      GPRE_NOD list, *ptr;
      SCHAR p_name[NAME_SIZE + 1], db_name[NAME_SIZE + 1],
            owner_name[NAME_SIZE + 1];
      BOOLEAN paren;

      request = MAKE_REQUEST(REQ_procedure);
      par_options(&request->req_trans);
      SQL_relation_name(p_name, db_name, owner_name);
      procedure = SQL_procedure(request, p_name, db_name, owner_name, TRUE);
      inputs = outputs = 0;
      values = NULL;

      if (!KEYWORD(KW_RETURNING) && !KEYWORD(KW_SEMI_COLON)) {
            /* parse input references */

            paren = MATCH(KW_LEFT_PAREN);
            field = procedure->prc_inputs;
            ref_ptr = &request->req_values;
            do {
                  if (MATCH(KW_NULL))
                        PUSH(MAKE_NODE(nod_null, 0), &values);
                  else {
                        *ref_ptr = reference = SQE_parameter(request, FALSE);
                        reference->ref_field = field;
                        PUSH(MSC_unary(nod_value, (GPRE_NOD) reference), &values);
                        ref_ptr = &reference->ref_next;
                  }
                  if (field)
                        field = field->fld_next;
                  inputs++;
            } while (MATCH(KW_COMMA));
            if (paren)
                  EXP_match_paren();
      }

      if (MATCH(KW_RETURNING)) {
            /* parse output references */

            paren = MATCH(KW_LEFT_PAREN);
            field = procedure->prc_outputs;
            ref_ptr = &request->req_references;
            do {
                  *ref_ptr = reference = SQE_variable(request, FALSE);
                  if (reference->ref_field = field)
                        field = field->fld_next;
                  ref_ptr = &reference->ref_next;
                  outputs++;
            } while (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");

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

      action = MAKE_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;
      ACT action;
      RSE select;
      TEXT s[ERROR_LENGTH];

      request = MAKE_REQUEST(REQ_for);
      par_options(&request->req_trans);
      request->req_rse = select = SQE_select(request, FALSE);

      if (!MATCH(KW_SEMI_COLON)) {
            sprintf(s, "Expected ';', got %s.", token.tok_string);
            CPR_warn(s);
      }

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

      action = MAKE_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(TEXT* base_directory)
{

      if (MATCH(KW_TRANSACTION))
            return act_set_transaction();

      if (MATCH(KW_NAMES))
            return act_set_names();

      if (MATCH(KW_STATISTICS))
            return act_set_statistics();

      if (MATCH(KW_SCHEMA) || MATCH(KW_DATABASE))
            return PAR_database((USHORT) TRUE, base_directory);

      if (MATCH(KW_GENERATOR))
            return act_set_generator();

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

      SYNTAX_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;
      USHORT dialect;

      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = ACT_sql_dialect;

      dialect = EXP_USHORT_ordinal(0 /*FALSE*/);
      if ((dialect < 1) || (dialect > 3))
            SYNTAX_ERROR("SQL DIALECT 1,2 or 3");

      if (isc_databases && dialect != compiletime_db_dialect
            && 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 (isc_databases && dialect != compiletime_db_dialect) {
            char warn_mesg[100];
            sprintf(warn_mesg,
                        "Client dialect set to %d. Compiletime database dialect is %d\n",
                        dialect, compiletime_db_dialect);
            CPR_warn(warn_mesg);
      }

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

//  Needed because subsequent parsing pass1 looks at sw_Sql_dialect value 
      sw_sql_dialect = dialect;
      dialect_specified = 1;

      ADVANCE_TOKEN;
      return action;
}


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

static ACT act_set_generator(void)
{
      ACT action;
      SGEN setgen;
      GPRE_REQ request;
      SCHAR s[128];

      request = MAKE_REQUEST(REQ_set_generator);
      action = (ACT) MAKE_ACTION(request, ACT_s_start);
      setgen = (SGEN) ALLOC(SGEN_LEN);

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

      if (token.tok_length > NAME_SIZE)
            PAR_error("Generator name too long");

      setgen->sgen_name = (TEXT *) ALLOC(token.tok_length + 1);
      SQL_resolve_identifier("<identifier>", setgen->sgen_name);
      if (!MET_generator(setgen->sgen_name, request->req_database)) {
            sprintf(s, "generator %s not found", token.tok_string);
            PAR_error(s);
      }
      ADVANCE_TOKEN;
      MATCH(KW_TO);
      if ((sw_sql_dialect == SQL_DIALECT_V5) || (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;
      }
      action->act_whenever = gen_whenever();
      action->act_object = (REF) setgen;
      return action;
}


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

static ACT act_set_names(void)
{
      ACT action;
      TEXT *value;
      DBB db;
      char buffer[256];

      if (sw_auto)
            CPR_warn("SET NAMES requires -manual switch to gpre.");

      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = ACT_noop;

      if (MATCH(KW_COLON)) {
            /* User is specifying a host variable or string as
             * the character set.  Make this the run-time set.
             */
            value = PAR_native_value(FALSE, FALSE);
            for (db = isc_databases; db; db = db->dbb_next) {
                  if (db->dbb_r_lc_ctype) {
                        sprintf(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 (token.tok_type == tok_ident) {
            /* User is specifying the name of a character set */
            /* Make this the compile time character set */

            value = (TEXT *) ALLOC(token.tok_length + 1);
            COPY(token.tok_string, token.tok_length, value);
            value[token.tok_length] = '\0';

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

            module_lc_ctype = value;
            for (db = isc_databases; db; db = db->dbb_next) {
                  if (db->dbb_c_lc_ctype) {
                        sprintf(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(token.tok_symbol, SYM_charset)))
                              PAR_error("The named CHARACTER SET was not found");
                  }
            }
            CPR_token();
      }
      else
            SYNTAX_ERROR("<character set name> or :<hostvar>");

      return action;
}


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

static ACT act_set_statistics(void)
{
      ACT action;
      STS stats;
      GPRE_REQ request;

      request = MAKE_REQUEST(REQ_ddl);
      action = (ACT) MAKE_ACTION(request, ACT_statistics);
      stats = (STS) ALLOC(STS_LEN);

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

      if (MATCH(KW_INDEX)) {
            stats->sts_flags = STS_index;
            stats->sts_name = (STR) ALLOC(NAME_SIZE + 1);
            SQL_resolve_identifier("<index name>", (TEXT *) stats->sts_name);
            if (token.tok_length > NAME_SIZE)
                  PAR_error("Index name too long");
            ADVANCE_TOKEN;
      }
      else
            SYNTAX_ERROR("INDEX");

      action->act_object = (REF) stats;
      return action;
}


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

static ACT act_set_transaction(void)
{
      ACT action;
      GPRE_TRA trans;

      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = ACT_start;

      trans = (GPRE_TRA) ALLOC(TRA_LEN);

      if (MATCH(KW_NAME))
            trans->tra_handle = PAR_native_value(FALSE, TRUE);

//  Get all the transaction parameters 

      while (TRUE) {
            if (MATCH(KW_ISOLATION)) {
                  MATCH(KW_LEVEL);
                  if (!par_transaction_modes(trans, TRUE))
                        SYNTAX_ERROR("SNAPSHOT");

                  continue;
            }

            if (par_transaction_modes(trans, FALSE))
                  continue;

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

            if (MATCH(KW_WAIT))
                  continue;

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

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

            break;
      }

//  send out for the list of reserved relations 

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

      CMP_t_start(trans);
      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)
{
      ACT action;
      TEXT *transaction;

      par_options(&transaction);
      MATCH(KW_WORK);
      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = type;
      action->act_whenever = gen_whenever();
      action->act_object = (REF) transaction;

      if (MATCH(KW_RELEASE)) {
            type = (type == ACT_commit) ? ACT_finish : ACT_rfinish;
            if (transaction) {
                  action->act_rest = (ACT) ALLOC(ACT_LEN);
                  action->act_rest->act_type = type;
            }
            else
                  action->act_type = type;
      }
      else if ((type == ACT_commit) && (MATCH(KW_RETAIN))) {
            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)
{
      ACT action, slice_action;
      GPRE_REQ request, slice_request;
      GPRE_REL relation;
      RSE rse;
      GPRE_CTX input_context, update_context;
      GPRE_NOD set_list, *end_list, set_item, modify, *ptr;
      UPD update;
      LLS stack;
      SYM alias;
      REF field_ref, req_ref;
      SLC slice;
      SCHAR *transaction, r_name[NAME_SIZE + 1], db_name[NAME_SIZE + 1],
            owner_name[NAME_SIZE + 1], *str_1, *str_2;
      SSHORT where, count, i, found = FALSE, trans_nm_len;

      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. 

      SQL_relation_name(r_name, db_name, owner_name);

//  Parse the optional alias (context variable) 

      alias = (token.tok_symbol) ? NULL : PAR_symbol(SYM_dummy);

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

      if (!MATCH(KW_SET))
            SYNTAX_ERROR("SET");

      request = MAKE_REQUEST(REQ_mass_update);
      relation = SQL_relation(request, r_name, db_name, owner_name, TRUE);
      input_context = MAKE_CONTEXT(request);
      input_context->ctx_relation = relation;
      if (alias) {
            alias->sym_type = SYM_context;
            alias->sym_object = input_context;
            HSH_insert(alias);
            token.tok_symbol = HSH_lookup(token.tok_string);
      }

      stack = NULL;
      count = 0;

      do {
            set_item = MAKE_NODE(nod_assignment, 2);
            set_item->nod_arg[1] = SQE_field(NULL, FALSE);
            if (!MATCH(KW_EQUALS))
                  SYNTAX_ERROR("assignment operator");
            if (MATCH(KW_NULL))
                  set_item->nod_arg[0] = MAKE_NODE(nod_null, 0);
            else
                  set_item->nod_arg[0] = SQE_value(request, FALSE, 0, 0);
            PUSH(set_item, &stack);
            count++;
      } while (MATCH(KW_COMMA));

      set_list = MAKE_NODE(nod_list, count);
      ptr = end_list = set_list->nod_arg + count;

      while (stack)
            *--ptr = (GPRE_NOD) 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 

      if ((where = MATCH(KW_WITH)) && MATCH(KW_CURRENT)) {
            if (!MATCH(KW_OF))
                  SYNTAX_ERROR("OF cursor");

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

            update = (UPD) 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 */
                        trans_nm_len = strlen(request->req_trans);
                        str_2 = transaction = (SCHAR *) ALLOC(trans_nm_len + 1);
                        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 */

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

            if (i == rse->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++) {
                  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;
            update->upd_update = update_context = MAKE_CONTEXT(request);
            update_context->ctx_relation = relation;
            update->upd_assignments = set_list;

            action = MAKE_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++) {
                  set_item = *ptr;
                  SQE_resolve(set_item->nod_arg[1], request, 0);
                  field_ref = (REF) ((set_item->nod_arg[1])->nod_arg[0]);
                  if ((slice_action = (ACT) field_ref->ref_slice) &&
                        (slice = (SLC) slice_action->act_object)) {
                        /* These requests got lost in freeing the main request  */

                        slice_request = slice_action->act_request;
                        slice_request->req_next = requests;
                        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 */

                        found = FALSE;
                        for (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 

      request->req_rse = rse = (RSE) ALLOC(RSE_LEN(1));
      rse->rse_count = 1;
      rse->rse_context[0] = input_context;

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

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

//  Process boolean, if any 

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

//  Resolve update fields to update context 

      request->req_update = update_context = MAKE_CONTEXT(request);
      update_context->ctx_relation = relation;

      if (alias)
            alias->sym_object = update_context;

      for (ptr = set_list->nod_arg; ptr < end_list; ptr++) {
            set_item = *ptr;
            SQE_resolve(set_item->nod_arg[1], request, 0);
            field_ref = (REF) ((set_item->nod_arg[1])->nod_arg[0]);
            if (slice_action = (ACT) field_ref->ref_slice) {
                  /* 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 == sw_sql_dialect) {
                  USHORT field_dtype;
                  int arg_num;
                  for (arg_num = 0; arg_num <= 1; arg_num++)
                        if (nod_field == set_item->nod_arg[arg_num]->nod_type) {
                              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)) {
                                    dialect1_bad_type(field_dtype);
                              }
                        }
            }

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

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

      action = MAKE_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)
{
      USHORT condition, l;
      TEXT *p, *q;
      SWE label;
      ACT action;

      whenever_list = NULL;

//  Pick up condition 

      if (MATCH(KW_SQLERROR))
            condition = SWE_error;
      else if (MATCH(KW_SQLWARNING))
            condition = SWE_warning;
      else if (MATCH(KW_NOT) && MATCH(KW_FOUND))
            condition = SWE_not_found;
      else
            SYNTAX_ERROR("WHENEVER condition");

//  Pick up action 

      if (MATCH(KW_CONTINUE))
            label = NULL;
      else if ((MATCH(KW_GO) && MATCH(KW_TO)) || MATCH(KW_GOTO)) {
            MATCH(KW_COLON);
            l = token.tok_length;
            label = (SWE) ALLOC(sizeof(struct swe) + l);
            label->swe_condition = condition;
            if (label->swe_length = l) {
                  p = label->swe_label;
                  q = token.tok_string;
                  do
                        *p++ = *q++;
                  while (--l);
            }
            ADVANCE_TOKEN;
            label->swe_condition = condition;
      }
      else
            SYNTAX_ERROR("GO TO or CONTINUE");

//  Set up condition vector 

      whenever[condition] = label;

      action = (ACT) ALLOC(ACT_LEN);
      action->act_type = ACT_noop;

      MATCH(KW_SEMI_COLON);
      return action;
}


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

static BOOLEAN check_filename( TEXT * name)
{
      USHORT l;
      TEXT *p, file_name[256];

      if (!(l = strlen(name)))
            return TRUE;

      for (p = name; *p; p++)
            if (p[0] == ':' && p[1] == ':')
                  return FALSE;

      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(
                                     TEXT ** user,
                                     TEXT ** password,
                                     TEXT ** sql_role,
                                     TEXT ** lc_messages, USHORT * buffers)
{
      TEXT *s;

      for (;;) {
            if (MATCH(KW_CACHE)) {
                  *buffers = atoi(token.tok_string);
                  ADVANCE_TOKEN;
                  MATCH(KW_BUFFERS);
            }
            else if (MATCH(KW_USER))
                  *user = SQL_var_or_string(FALSE);
            else if (MATCH(KW_PASSWORD))
                  *password = SQL_var_or_string(FALSE);
            else if (MATCH(KW_ROLE)) {
                  if (token.tok_type == tok_ident) {
                        s = (TEXT *) ALLOC(token.tok_length + 3);
                                                                     /** extra bytes for quotes and NULL **/
                        SQL_resolve_identifier("<Role Name>", s);
                        s[0] = '\"';
                        strcpy(s + 1, token.tok_string);
                        strcat(s, "\"");
                        *sql_role = s;
                  }
                  else
                        *sql_role = SQL_var_or_string(FALSE);
            }
            else if (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;
      TEXT *string;
      TEXT err_string[256];

      file = (FIL) ALLOC(sizeof(struct fil));
      if (QUOTED(token.tok_type)) {
            file->fil_name = string = (TEXT *) ALLOC(token.tok_length + 1);
            COPY(token.tok_string, token.tok_length, string);
            ADVANCE_TOKEN;
      }
      else
            SYNTAX_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 (MATCH(KW_LENGTH)) {
            file->fil_length = EXP_ULONG_ordinal(TRUE);
            if (file->fil_length < MIN_CACHE_BUFFERS) {
                  sprintf(err_string, "Minimum of %d cache pages required",
                              MIN_CACHE_BUFFERS);
                  PAR_error(err_string);
            }
            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;
      TEXT *string;

      file = (FIL) ALLOC(sizeof(struct fil));
      if (QUOTED(token.tok_type)) {
            file->fil_name = string = (TEXT *) ALLOC(token.tok_length + 1);
            COPY(token.tok_string, token.tok_length, string);
            token.tok_length += 2;
            ADVANCE_TOKEN;
      }
      else
            SYNTAX_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 (MATCH(KW_LENGTH)) {
                  MATCH(KW_EQUALS);
                  file->fil_length = EXP_ULONG_ordinal(TRUE);
                  MATCH(KW_PAGES);
                  MATCH(KW_PAGE);
            }
            else if (MATCH(KW_STARTS) || MATCH(KW_STARTING)) {
                  MATCH(KW_AT);
                  MATCH(KW_PAGE);
                  file->fil_start = EXP_ULONG_ordinal(TRUE);
            }
            else
                  break;
      }

      return file;
}


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

static FIL define_log_file( BOOLEAN log_serial)
{
      FIL file;
      TEXT *string;
      TEXT err_string[256];

      file = (FIL) ALLOC(sizeof(struct fil));
      if (QUOTED(token.tok_type)) {
            file->fil_name = string = (TEXT *) ALLOC(token.tok_length + 1);
            COPY(token.tok_string, token.tok_length, string);
            ADVANCE_TOKEN;
      }
      else
            SYNTAX_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 (MATCH(KW_SIZE)) {
                  MATCH(KW_EQUALS);
                  file->fil_length = EXP_ULONG_ordinal(TRUE);
            }
// **** REMOVED for now from the product.
//   else if (MATCH (KW_RAW_PARTITIONS))
// { 
// if (log_serial)
//    PAR_error ("Partitions not supported in series of log file specification");
// MATCH (KW_EQUALS);
// file->fil_partitions = EXP_USHORT_ordinal (TRUE);
// file->fil_flags |= FIL_raw;
// } 
//***
            else
                  break;
      }

      if (file->fil_partitions) {
            if (!file->fil_length) {
                  sprintf(err_string,
                              "Total length of the partitioned log %s must be specified",
                              file->fil_name);
                  PAR_error(err_string);
            }

            if (PARTITION_SIZE(OneK * file->fil_length, file->fil_partitions) <
                  OneK * MIN_LOG_LENGTH) {
                  sprintf(err_string, "log partition size too small for %s",
                              file->fil_name);
                  PAR_error(err_string);
            }
      }
      else {
            if ((file->fil_length) && (file->fil_length < MIN_LOG_LENGTH)) {
                  /* msg 336: Minimum log length should be MIN_LOG_LENGTH Kbytes */
                  sprintf(err_string,
                              "Minimum log length should be %d Kbytes", MIN_LOG_LENGTH);
                  PAR_error(err_string);
            }
      }

      return file;
}


static DBB dup_dbb( DBB db)
{

// ****************************************
//  
//          d u p _ d b b
//  
// ****************************************
//  
//          dirty duplication of a dbb.
//          just memcpy as no memory 
//          is freed in gpre.
//  
// *************************************
      DBB newdb;

      if (!db)
            return NULL;
      newdb = (DBB) ALLOC(DBB_LEN);
      memcpy((SCHAR *) newdb, (SCHAR *) db, (size_t) DBB_LEN);

      return newdb;
}


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

static void error( TEXT * string, TEXT * string2)
{
      TEXT buffer[256];

      sprintf(buffer, string, string2);
      PAR_error(buffer);
}


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

static TEXT *extract_string( BOOLEAN advance_token)
{
      TEXT *string = NULL;

      switch (sw_sql_dialect) {
      case 1:
            if (!QUOTED(token.tok_type))
                  SYNTAX_ERROR("<string>");
            break;

      default:
            if (!SINGLE_QUOTED(token.tok_type))
                  SYNTAX_ERROR("<string>");
            break;
      }

      string = (TEXT *) ALLOC(token.tok_length + 1);
      COPY(token.tok_string, token.tok_length, string);
      if (advance_token)
            ADVANCE_TOKEN;
      return string;
}



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

static SWE gen_whenever(void)
{
      SWE label, prior, proto;
      USHORT i, l;
      TEXT *p, *q;

      if (whenever_list)
            return whenever_list;

      whenever_list = label = NULL;

      for (i = 0; i < SWE_max; i++)
            if (proto = whenever[i]) {
                  prior = label;
                  l = proto->swe_length;
                  label = (SWE) ALLOC(sizeof(struct swe) + l);
                  label->swe_next = prior;
                  label->swe_condition = proto->swe_condition;
                  if (l) {
                        p = label->swe_label;
                        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)
{
      REF var_ref, field_ref, reference;
      GPRE_NOD *var_ptr, *fld_ptr, *end;
      GPRE_FLD field;
      GPRE_REQ slice_req;
      SSHORT found = FALSE;

      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");

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

      for (end = fld_ptr + var_list->nod_count; fld_ptr < end;
             fld_ptr++, var_ptr++) {
            var_ref = (REF) * var_ptr;
            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];
            }
            else
                  field_ref = 0;
            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   */

                  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)
{
      GPRE_FLD field;
      char s[ERROR_LENGTH];

      SQL_resolve_identifier("<column name>", s);
      field = MET_make_field(s, 0, 0, TRUE);
      field->fld_relation = relation;
      field->fld_flags |= FLD_meta;
      ADVANCE_TOKEN;

      return field;
}


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

static IND make_index( GPRE_REQ request, TEXT * string)
{
      IND index;
      TEXT s[ERROR_LENGTH];

      if ((isc_databases) && (!(isc_databases->dbb_next))) {
            strcpy(s, string);
            index = MET_make_index(s);
            if (request)
                  request->req_database = 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, TEXT * relation_name)
{
      GPRE_REL relation;
      TEXT r[ERROR_LENGTH];

      if (isc_databases && !isc_databases->dbb_next) {
            strcpy(r, relation_name);

            relation = MET_make_relation(r);
            relation->rel_database = isc_databases;
            relation->rel_meta = TRUE;

            if (request)
                  request->req_database = 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)
{
      GPRE_NOD *ptr, *end_ptr;
      REF ref1, ref2;
      MEL element;

      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:
            element = (MEL) expr->nod_arg[0];
            pair(element->mel_expr, field_expr);
            return;

      case nod_field:
      case nod_literal:
            return;
      }

      ptr = expr->nod_arg;
      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)
{
      SLONG rangeh, rangel;
      SSHORT i = 0;
      ARY array_info;

//  Pick up ranges 

      array_info = field->fld_array_info;

      while (TRUE) {
            rangel = EXP_SSHORT_ordinal(TRUE);
            if (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 (MATCH(KW_R_BRCKET))
                  break;

            if (!MATCH(KW_COMMA))
                  SYNTAX_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)
{
      SYM symbol;

      if (!MATCH(KW_CHAR))
            SYNTAX_ERROR("CHARACTER SET");

      if (!MATCH(KW_SET))
            SYNTAX_ERROR("CHARACTER SET");

      if (token.tok_type != tok_ident)
            SYNTAX_ERROR("<character set name>");

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

      ADVANCE_TOKEN;

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


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

static void par_computed( GPRE_REQ request, GPRE_FLD field)
{
      CMPF cmp;
      struct gpre_fld save_fld;

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

      save_fld = *field;

      cmp = (CMPF) ALLOC(CMPF_LEN);
      cmp->cmpf_text = CPR_start_text();
      cmp->cmpf_boolean = SQE_value(request, FALSE, 0, 0);
      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( SYM * symbol_ptr)
{
      SYM symbol;
      TEXT t_cur[128];

// ** 
//   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.c 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>", t_cur);
      token.tok_symbol = symbol = HSH_lookup(token.tok_string);
      if (symbol && symbol->sym_type == SYM_keyword)
            token.tok_keyword = (KWWORDS) symbol->sym_keyword;
      else
            token.tok_keyword = KW_none;

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


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

static DYN par_dynamic_cursor(void)
{
      SYM symbol;
      TEXT t_cur[128];

      if (token.tok_symbol == NULL) {
            SQL_resolve_identifier("<cursor name>", t_cur);
            token.tok_symbol = symbol = HSH_lookup(token.tok_string);
            if (symbol && symbol->sym_type == SYM_keyword)
                  token.tok_keyword = (KWWORDS) symbol->sym_keyword;
            else
                  token.tok_keyword = KW_none;
      }
      if (symbol = MSC_find_symbol(token.tok_symbol, SYM_dyn_cursor)) {
            ADVANCE_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;
// *IND           index; 
      CNSTRT *cnstrt;
      int in_constraints;
      GPRE_NOD literal_node;

      field = make_field(relation);

      SQL_par_field_dtype(request, field, FALSE);

      if (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 (token.tok_keyword == KW_DEFAULT) {
            field->fld_default_source = CPR_start_text();
            ADVANCE_TOKEN;

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

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


//  Check for any column level constraints 

      cnstrt = &field->fld_constraints;
      in_constraints = TRUE;

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

            default:
                  in_constraints = FALSE;
                  break;
            }
      }
//    if (MATCH (KW_NOT))
//   if (MATCH (KW_NULL))
// { 
// field->fld_flags |= FLD_not_null;
// if (MATCH (KW_UNIQUE))
//    {
//    index = (IND) 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
// SYNTAX_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)
{
      enum kwwords keyword;
      CNSTRT cnstrt;
      STR field_name;

      cnstrt = (CNSTRT) ALLOC(CNSTRT_LEN);

      if (token.tok_keyword == KW_CONSTRAINT) {
            ADVANCE_TOKEN;
            cnstrt->cnstrt_name = (STR) ALLOC(NAME_SIZE + 1);
            SQL_resolve_identifier("<constraint name>",
                                             (TEXT *) cnstrt->cnstrt_name);
            if (token.tok_length > NAME_SIZE)
                  PAR_error("Constraint name too long");
            ADVANCE_TOKEN;
      }

      switch (keyword = token.tok_keyword) {
      case KW_NOT:
            ADVANCE_TOKEN;
            if (!MATCH(KW_NULL))
                  SYNTAX_ERROR("NULL");
            cnstrt->cnstrt_type = CNSTRT_NOT_NULL;
            for_field->fld_flags |= FLD_not_null;
            break;

      case KW_PRIMARY:
      case KW_UNIQUE:
      case KW_REFERENCES:
            ADVANCE_TOKEN;
            if (keyword == KW_PRIMARY) {
                  if (!MATCH(KW_KEY))
                        SYNTAX_ERROR("KEY");
                  cnstrt->cnstrt_type = CNSTRT_PRIMARY_KEY;
            }
            else if (keyword == KW_REFERENCES) {
                  cnstrt->cnstrt_type = CNSTRT_FOREIGN_KEY;
            }
            else
                  cnstrt->cnstrt_type = CNSTRT_UNIQUE;

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

            field_name = (STR) ALLOC(NAME_SIZE + 1);
            strcpy((char *) field_name, for_field->fld_symbol->sym_string);
            PUSH((GPRE_NOD) field_name, &cnstrt->cnstrt_fields);

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

                  cnstrt->cnstrt_referred_rel = (STR) ALLOC(NAME_SIZE + 1);
                  SQL_resolve_identifier("referred <table name>",
                                                   (TEXT *) cnstrt->cnstrt_referred_rel);
                  if (token.tok_length > NAME_SIZE)
                        PAR_error("Referred table name too long");
                  ADVANCE_TOKEN;

                  if (MATCH(KW_LEFT_PAREN)) {
                        /* Field specified for referred relation  */

                        field_name = (STR) ALLOC(NAME_SIZE + 1);
                        SQL_resolve_identifier("<column name>", (TEXT *) field_name);
                        PUSH((GPRE_NOD) field_name, &cnstrt->cnstrt_referred_fields);
                        CPR_token();
                        EXP_match_paren();
                  }

                  if (token.tok_keyword == KW_ON) {
                        par_fkey_extension(cnstrt);
                        ADVANCE_TOKEN;
                        if (token.tok_keyword == KW_ON) {
                              par_fkey_extension(cnstrt);
                              ADVANCE_TOKEN;
                        }
                  }
            }
            break;

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

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

      return cnstrt;
}


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

static BOOLEAN par_into( DYN statement)
{

      if (!MATCH(KW_INTO))
            return FALSE;

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

      if (!MATCH(KW_DESCRIPTOR))
            SYNTAX_ERROR("DESCRIPTOR");

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

      return TRUE;
}


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

static void par_options( TEXT ** transaction)
{

      *transaction = NULL;

      if (!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)
{
      int n1, n2;

      MATCH(KW_EQUALS);
      n2 = n1 = EXP_USHORT_ordinal(FALSE);

      if (n1 <= 1024)
            n2 = 1024;
      else if (n1 <= 2048)
            n2 = 2048;
      else if (n1 <= 4096)
            n2 = 4096;
      else if (n1 <= 8192)
            n2 = 8192;
      else
            SYNTAX_ERROR("<page size> in range 1024 to 8096");
      ADVANCE_TOKEN;

      return n2;
}


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

static GPRE_REL par_relation( GPRE_REQ request)
{
      SCHAR r_name[NAME_SIZE + 1], db_name[NAME_SIZE + 1],
            owner_name[NAME_SIZE + 1];

      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;

      statement = (DYN) ALLOC(DYN_LEN);
      statement->dyn_statement_name = PAR_symbol(SYM_dummy);
      statement->dyn_database = 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)
{
      enum kwwords keyword;

//  
//   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 } ]
//  

      assert(token.tok_keyword == KW_ON);
            assert(cnstrt != NULL);

      ADVANCE_TOKEN;

      switch (keyword = token.tok_keyword) {
      case KW_DELETE:
            /* NOTE: action must be defined only once */
            if (cnstrt->cnstrt_fkey_def_type & REF_DELETE_ACTION)
                  SYNTAX_ERROR("UPDATE");
            else
                  cnstrt->cnstrt_fkey_def_type |= REF_DELETE_ACTION;
            break;
      case KW_UPDATE:
            /* NOTE: action must be defined only once */
            if (cnstrt->cnstrt_fkey_def_type & REF_UPDATE_ACTION)
                  SYNTAX_ERROR("DELETE");
            else
                  cnstrt->cnstrt_fkey_def_type |= REF_UPDATE_ACTION;
            break;
      default:
            /* unexpected keyword */
            SYNTAX_ERROR("UPDATE or DELETE");
            break;
      }

      ADVANCE_TOKEN;

      switch (token.tok_keyword) {
      case KW_NO:
            ADVANCE_TOKEN;
            if (token.tok_keyword != KW_ACTION)
                  SYNTAX_ERROR("ACTION");
            else if (keyword == KW_DELETE)
                  cnstrt->cnstrt_fkey_def_type |= REF_DEL_NONE;
            else
                  cnstrt->cnstrt_fkey_def_type |= REF_UPD_NONE;
            break;
      case KW_CASCADE:
            if (keyword == KW_DELETE)
                  cnstrt->cnstrt_fkey_def_type |= REF_DEL_CASCADE;
            else
                  cnstrt->cnstrt_fkey_def_type |= REF_UPD_CASCADE;
            break;
      case KW_SET:
            ADVANCE_TOKEN;
            switch (token.tok_keyword) {
            case KW_DEFAULT:
                  if (keyword == KW_DELETE)
                        cnstrt->cnstrt_fkey_def_type |= REF_DEL_SET_DEFAULT;
                  else
                        cnstrt->cnstrt_fkey_def_type |= REF_UPD_SET_DEFAULT;
                  break;
            case KW_NULL:
                  if (keyword == KW_DELETE)
                        cnstrt->cnstrt_fkey_def_type |= REF_DEL_SET_NULL;
                  else
                        cnstrt->cnstrt_fkey_def_type |= REF_UPD_SET_NULL;
                  break;
            default:
                  /* unexpected keyword */
                  SYNTAX_ERROR("NULL or DEFAULT");
                  break;
            }
            break;
      default:
            /* unexpected keyword */
            SYNTAX_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)
{
      enum kwwords keyword;
      CNSTRT cnstrt;
      LLS *fields;
      STR field_name;
      USHORT num_for_key_flds = 0, num_prim_key_flds = 0;

      cnstrt = (CNSTRT) ALLOC(CNSTRT_LEN);

      if (token.tok_keyword == KW_CONSTRAINT) {
            ADVANCE_TOKEN;
            cnstrt->cnstrt_name = (STR) ALLOC(NAME_SIZE + 1);
            SQL_resolve_identifier("<constraint name>",
                                             (TEXT *) cnstrt->cnstrt_name);
            if (token.tok_length > NAME_SIZE)
                  PAR_error("Constraint name too long");
            ADVANCE_TOKEN;
      }

      switch (keyword = token.tok_keyword) {
      case KW_PRIMARY:
      case KW_UNIQUE:
      case KW_FOREIGN:
            ADVANCE_TOKEN;
            if (keyword == KW_PRIMARY) {
                  if (!MATCH(KW_KEY))
                        SYNTAX_ERROR("KEY");
                  cnstrt->cnstrt_type = CNSTRT_PRIMARY_KEY;
            }
            else if (keyword == KW_FOREIGN) {
                  if (!MATCH(KW_KEY))
                        SYNTAX_ERROR("KEY");
                  cnstrt->cnstrt_type = CNSTRT_FOREIGN_KEY;
            }
            else
                  cnstrt->cnstrt_type = CNSTRT_UNIQUE;

            EXP_left_paren(0);

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

            fields = &cnstrt->cnstrt_fields;
            do {
                  field_name = (STR) ALLOC(NAME_SIZE + 1);
                  SQL_resolve_identifier("<column name>", (TEXT *) field_name);
                  PUSH((GPRE_NOD) field_name, fields);
                  fields = &(*fields)->lls_next;
                  ++num_for_key_flds;
                  CPR_token();
            } while (MATCH(KW_COMMA));

            if (keyword != KW_FOREIGN)
                  num_for_key_flds = 0;

            EXP_match_paren();
            if (keyword == KW_FOREIGN) {
                  if (!MATCH(KW_REFERENCES))
                        SYNTAX_ERROR("REFERENCES");

                  /* Relation name for foreign key  */

                  cnstrt->cnstrt_referred_rel = (STR) ALLOC(NAME_SIZE + 1);
                  SQL_resolve_identifier("referred <table name>",
                                                   (TEXT *) cnstrt->cnstrt_referred_rel);
                  if (token.tok_length > NAME_SIZE)
                        PAR_error("Referred table name too long");
                  ADVANCE_TOKEN;

                  cnstrt->cnstrt_referred_fields = NULL;

                  if (MATCH(KW_LEFT_PAREN)) {
                        /* Fields specified for referred relation  */

                        fields = &cnstrt->cnstrt_referred_fields;
                        do {
                              field_name = (STR) ALLOC(NAME_SIZE + 1);
                              SQL_resolve_identifier("<column name>",
                                                               (TEXT *) field_name);
                              PUSH((GPRE_NOD) field_name, fields);
                              fields = &(*fields)->lls_next;
                              ++num_prim_key_flds;
                              CPR_token();
                        } while (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 (cnstrt->cnstrt_referred_fields != NULL &&
                        num_prim_key_flds != num_for_key_flds)
                              PAR_error
                              ("FOREIGN KEY column count does not match PRIMARY KEY");
                  if (token.tok_keyword == KW_ON) {
                        par_fkey_extension(cnstrt);
                        ADVANCE_TOKEN;
                        if (token.tok_keyword == KW_ON) {
                              par_fkey_extension(cnstrt);
                              ADVANCE_TOKEN;
                        }
                  }
            }                                   /* if KW_FOREIGN */
            break;

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

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

      return cnstrt;
}


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

static BOOLEAN par_transaction_modes( GPRE_TRA trans, BOOLEAN expect_iso)
{

      if (MATCH(KW_READ)) {
            if (MATCH(KW_ONLY)) {
                  if (expect_iso)
                        SYNTAX_ERROR("SNAPSHOT");

                  trans->tra_flags |= TRA_ro;
                  return TRUE;
            }
            else if (MATCH(KW_WRITE)) {
                  if (expect_iso)
                        SYNTAX_ERROR("SNAPSHOT");

                  return TRUE;
            }

            if (!(MATCH(KW_COMMITTED) || MATCH(KW_UNCOMMITTED)))
                  SYNTAX_ERROR("COMMITTED");

            trans->tra_flags |= TRA_read_committed;

            if (MATCH(KW_NO)) {
                  if (MATCH(KW_VERSION))
                        return TRUE;
                  else if (MATCH(KW_WAIT)) {
                        trans->tra_flags |= TRA_nw;
                        return TRUE;
                  }
                  else
                        SYNTAX_ERROR("WAIT or VERSION");
            }

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

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

                  MATCH(KW_STABILITY);
                  return TRUE;
            }
            else
                  return TRUE;
      }
      return FALSE;
}


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

static BOOLEAN par_using( DYN statement)
{

      if (!MATCH(KW_USING))
            return FALSE;

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

      if (MATCH(KW_DESCRIPTOR))
            statement->dyn_sqlda = PAR_native_value(FALSE, FALSE);
      else
            statement->dyn_using =
                  (GPRE_NOD) SQE_list(reinterpret_cast < pfn_SQE_list_cb >
                                       (SQE_variable), 0, FALSE);

      return TRUE;
}


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

static USHORT resolve_dtypes( KWWORDS typ, BOOLEAN sql_date)
{
      TEXT err_mesg[128];

      switch (typ) {
      case KW_DATE:
            if ((sw_ods_version < 10) || (sw_server_version < 6))
                  switch (sw_sql_dialect) {
                  case 1:
                        return dtype_timestamp;
                  case 2:
                        sprintf(err_mesg,
                                    "Encountered column type DATE which is ambiguous in dialect %d\n",
                                    sw_sql_dialect);
                        PAR_error(err_mesg);
                        return dtype_null;      /* TMN: FIX FIX */
                        /* return; */
                  default:
                        sprintf(err_mesg,
                                    "Encountered column type DATE which is not supported in ods version %d\n",
                                    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 (sw_sql_dialect) {
                  case 1:
                        return dtype_timestamp;
                  case 2:
                        sprintf(err_mesg,
                                    "Encountered column type DATE which is ambiguous in dialect %d\n",
                                    sw_sql_dialect);
                        PAR_error(err_mesg);
                        return dtype_null;      /* TMN: FIX FIX */
                        /* return; */
                  default:
                        return dtype_sql_date;
                  }
            }
            break;

      case KW_TIME:
            if ((sw_ods_version < 10) || (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_null;      /* 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_null;" to silence compiler, but
//  this is really a logic error we have to fix.
//  
      return dtype_null;
}


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

static BOOLEAN tail_database( enum act_t action_type, DBB database)
{
      FIL file;
      TEXT *string;
      BOOLEAN extend_dpb, logdefined;

//  parse options for the database parameter block 

      logdefined = FALSE;
      extend_dpb = FALSE;
      database->dbb_grp_cmt_wait = -1;

      while (TRUE) {
            if (MATCH(KW_LENGTH)) {
                  database->dbb_length = EXP_ULONG_ordinal(TRUE);
                  MATCH(KW_PAGES);
            }
            else if (MATCH(KW_USER)) {
                  if (QUOTED(token.tok_type)) {
                        database->dbb_c_user = string =
                              (TEXT *) ALLOC(token.tok_length + 1);
                        COPY(token.tok_string, token.tok_length, string);

                        string[token.tok_length - 2] = '\0';
                        ADVANCE_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 (sw_language == lang_c) {
                        if (!MATCH(KW_COLON))
                              SYNTAX_ERROR("<colon> or <quoted string>");
            /** Should I bother about dialects here ?? **/
                        if (QUOTED(token.tok_type))
                              SYNTAX_ERROR("<host variable>");
                        database->dbb_r_user = PAR_native_value(FALSE, FALSE);
                        extend_dpb = TRUE;
                  }
                  else
                        SYNTAX_ERROR("<quoted string>");

            }
            else if (MATCH(KW_PASSWORD)) {
                  if (QUOTED(token.tok_type)) {
                        database->dbb_c_password = string =
                              (TEXT *) ALLOC(token.tok_length + 1);
                        COPY(token.tok_string, token.tok_length, string);
                        string[token.tok_length] = '\0';
                        ADVANCE_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 (sw_language == lang_c) {
                        if (!MATCH(KW_COLON))
                              SYNTAX_ERROR("<colon> or <quoted string>");
                        if (QUOTED(token.tok_type))
                              SYNTAX_ERROR("<host variable>");
                        database->dbb_r_password = PAR_native_value(FALSE, FALSE);
                        extend_dpb = TRUE;
                  }
                  else
                        SYNTAX_ERROR("<quoted string>");
            }
            else if (MATCH(KW_DEFAULT)) {
                  if (!MATCH(KW_CHAR) || !MATCH(KW_SET))
                        SYNTAX_ERROR("CHARACTER SET");
                  database->dbb_def_charset = PAR_native_value(FALSE, FALSE);
            }
            else
                  break;
      }

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

//  parse add/drop items 

      while (TRUE) {
            MATCH(KW_ADD);
            if (MATCH(KW_DROP)) {
                  if (MATCH(KW_LOG_FILE))
                        database->dbb_flags |= DBB_drop_log;
                  else if (MATCH(KW_CASCADE))
                        database->dbb_flags |= DBB_cascade;
#ifdef FLINT_CACHE
                  else if (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 (MATCH (KW_DESCRIP))
//    database->dbb_flags     |= DBB_null_description;
// else if (MATCH (KW_SECURITY_CLASS))
//    database->dbb_flags |= DBB_null_security_class;
//***
            }
// ****
//   else if (KEYWORD (KW_DESCRIPTION))
// database->dbb_description = parse_description();
//   else if (MATCH (KW_SECURITY_CLASS))
// database->dbb_security_class = PARSE_symbol (tok_ident); 
//***
            else if (MATCH(KW_FILE)) {
                  file = define_file();
                  file->fil_next = database->dbb_files;
                  database->dbb_files = file;
            }
            else if (MATCH(KW_PAGE_SIZE) || 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 (MATCH(KW_CHECK_POINT_LEN)) {
                  MATCH(KW_EQUALS);
                  database->dbb_chkptlen = EXP_ULONG_ordinal(TRUE);
            }
            else if (MATCH(KW_NUM_LOG_BUFS)) {
                  MATCH(KW_EQUALS);
                  database->dbb_numbufs = EXP_USHORT_ordinal(TRUE);
            }
            else if (MATCH(KW_LOG_BUF_SIZE)) {
                  MATCH(KW_EQUALS);
                  database->dbb_bufsize = EXP_USHORT_ordinal(TRUE);
            }
            else if (MATCH(KW_GROUP_COMMIT_WAIT)) {
                  MATCH(KW_EQUALS);
                  database->dbb_grp_cmt_wait = EXP_ULONG_ordinal(TRUE);
            }
            else if (MATCH(KW_LOG_FILE)) {
                  if (logdefined)
                        PAR_error("log redefinition");
                  logdefined = TRUE;
                  if (MATCH(KW_LEFT_PAREN)) {
                        while (TRUE) {
                              file = define_log_file(FALSE);
                              file->fil_next = database->dbb_logfiles;
                              database->dbb_logfiles = file;
                              if (!MATCH(KW_COMMA)) {
                                    EXP_match_paren();
                                    break;
                              }
                        }

                        if (MATCH(KW_OVERFLOW))
                              database->dbb_overflow = define_log_file(TRUE);
                        else
                              PAR_error
                                    ("Overflow log specification required for this configuration");
                  }
                  else if (MATCH(KW_BASE_NAME)) {
                        database->dbb_flags |= DBB_log_serial;
                        database->dbb_logfiles = file = define_log_file(TRUE);
                  }
                  else
                        database->dbb_flags |= DBB_log_default;
            }
#ifdef FLINT_CACHE
            else if (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( TEXT * p, TEXT * q)
{
      UCHAR c;
      USHORT l;

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


//____________________________________________________________
//  
//  

void SQL_resolve_identifier( TEXT * err_mesg, TEXT * str)
{

      switch (sw_sql_dialect) {
      case 2:
            if (DOUBLE_QUOTED(token.tok_type))
                  PAR_error("Ambiguous use of double quotes in dialect 2");
      case 1:
            if (token.tok_type != tok_ident)
                  SYNTAX_ERROR(err_mesg);
            else
                  to_upcase(token.tok_string, str);
            break;
      case 3:
            if (DOUBLE_QUOTED(token.tok_type)) {
                  if (DOUBLE_QUOTES_ON(token.tok_string))
                        STRIP_QUOTES(token)
                              strcpy(str, token.tok_string);
            }
            else if (token.tok_type == tok_ident)
                  to_upcase(token.tok_string, str);
            else
                  SYNTAX_ERROR(err_mesg);
            break;
      }
      strcpy(token.tok_string, str);
}


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

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


} // extern "C"

Generated by  Doxygen 1.6.0   Back to index