//____________________________________________________________ // // 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.6 2002-11-30 17:40:24 hippoman Exp $ // #include "firebird.h" #include #include #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 #define MIN_CACHE_BUFFERS 250 #define DEF_CACHE_BUFFERS 1000 #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(void); 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 *); static FIL define_cache(void); 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(void) { 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); 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(); 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(""); 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(""); ADVANCE_TOKEN; break; case KW_SQL: if (sw_sql_dialect == 1) SYNTAX_ERROR(""); ADVANCE_TOKEN; if (token.tok_keyword == KW_DATE) ADVANCE_TOKEN; else SYNTAX_ERROR(""); keyword = KW_DATE; sql_date = TRUE; break; case KW_COMPUTED: if (udf) SYNTAX_ERROR(""); /* just return - actual parse is done later */ return; default: if (udf) { if (keyword == KW_CSTRING) ADVANCE_TOKEN; else SYNTAX_ERROR(""); } else { SQL_resolve_identifier("", 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(""); } // 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(""); 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("", 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("
", 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("
", 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(""); if (!MATCH(KW_COLON)) SYNTAX_ERROR(" or "); } 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(""); 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(""); } 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("", 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("", (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) { register ACT action; register 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(""); } 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(""); // 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(""); 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(""); } 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("", 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("", 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(""); 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]; TEXT *saved_name = NULL; 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("", 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("", 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("", 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 "); 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) { register 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 "); 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(""); 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(""); 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("", 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("", 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("", 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("", 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(": "); break; default: if ((!SINGLE_QUOTED(token.tok_type)) && (!MATCH(KW_COLON))) SYNTAX_ERROR(": "); 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; #endif GPRE_NOD offset_node = NULL; // 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", // 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("", 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(""); } } 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); 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(""); 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("", 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(": "); break; default: if ((!SINGLE_QUOTED(token.tok_type)) && (!MATCH(KW_COLON))) SYNTAX_ERROR(": "); 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 // static ACT act_set(void) { 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); 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("", 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 ; // 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(" or :"); 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("", (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("", 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; } } //____________________________________________________________ // // 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(""); 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; } //____________________________________________________________ // // 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(""); 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(""); 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(""); break; default: if (!SINGLE_QUOTED(token.tok_type)) SYNTAX_ERROR(""); 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("", 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(""); 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("", 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(""); 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("", 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(""); 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(""); } 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("", (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
", (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("", (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(" 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("", (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("", (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
", (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("", (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 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(" or "); /** Should I bother about dialects here ?? **/ if (QUOTED(token.tok_type)) SYNTAX_ERROR(""); database->dbb_r_user = PAR_native_value(FALSE, FALSE); extend_dpb = TRUE; } else SYNTAX_ERROR(""); } 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(" or "); if (QUOTED(token.tok_type)) SYNTAX_ERROR(""); database->dbb_r_password = PAR_native_value(FALSE, FALSE); extend_dpb = TRUE; } else SYNTAX_ERROR(""); } 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); #ifdef JPN_SJIS /* Do not upcase second byte of a sjis kanji character */ if (SJIS1(c) && (c = *p++) && (++l <= NAME_SIZE)) *q++ = c; #endif } *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"