//____________________________________________________________ // // PROGRAM: C Preprocessor // MODULE: sqe.cpp // DESCRIPTION: SQL expression parser // // The contents of this file are subject to the Interbase Public // License Version 1.0 (the "License"); you may not use this file // except in compliance with the License. You may obtain a copy // of the License at http://www.Inprise.com/IPL.html // // Software distributed under the License is distributed on an // "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express // or implied. See the License for the specific language governing // rights and limitations under the License. // // The Original Code was created by Inprise Corporation // and its predecessors. Portions created by Inprise Corporation are // Copyright (C) Inprise Corporation. // // All Rights Reserved. // Contributor(s): ______________________________________. // // Revision 1.3 2000/11/16 15:54:29 fsg // Added new switch -verbose to gpre that will dump // parsed lines to stderr // // Fixed gpre bug in handling row names in WHERE clauses // that are reserved words now (DATE etc) // (this caused gpre to dump core when parsing tan.e) // // Fixed gpre bug in handling lower case table aliases // in WHERE clauses for sql dialect 2 and 3. // (cause a core dump in a test case from C.R. Zamana) // // Mike Nordell - Reduce compiler warnings // Stephen W. Boyd - Added support for new features //____________________________________________________________ // #include "firebird.h" #include #include #include "../gpre/gpre.h" #include "../gpre/parse.h" #include "../gpre/cme_proto.h" #include "../gpre/cmp_proto.h" #include "../gpre/exp_proto.h" #include "../gpre/gpre_proto.h" #include "../gpre/hsh_proto.h" #include "../gpre/gpre_meta.h" #include "../gpre/msc_proto.h" #include "../gpre/par_proto.h" #include "../gpre/sqe_proto.h" #include "../gpre/sql_proto.h" #include "../common/utils_proto.h" struct scope { gpre_ctx* req_contexts; USHORT req_scope_level; // scope level for SQL subquery parsing USHORT req_in_aggregate; // now processing value expr for aggr USHORT req_in_select_list; // processing select list USHORT req_in_where_clause; // processing where clause USHORT req_in_having_clause; // processing having clause USHORT req_in_order_by_clause; // processing order by clause USHORT req_in_subselect; // processing a subselect clause }; static bool compare_expr(const gpre_nod*, const gpre_nod*); static gpre_nod* copy_fields(gpre_nod*, map*); static gpre_nod* explode_asterisk(gpre_nod*, int, gpre_rse*); static gpre_nod* explode_asterisk_all(gpre_nod*, int, gpre_rse*, bool); static gpre_fld* get_ref(gpre_nod*); static gpre_nod* implicit_any(gpre_req*, gpre_nod*, nod_t, nod_t); static gpre_nod* merge(gpre_nod*, gpre_nod*); static gpre_nod* merge_fields(gpre_nod*, gpre_nod*, int, bool); static gpre_nod* negate(gpre_nod*); static void pair(gpre_nod*, gpre_nod*); static gpre_ctx* par_alias_list(gpre_req*, gpre_nod*); static gpre_ctx* par_alias(gpre_req*, const TEXT*); static gpre_nod* par_and(gpre_req*, USHORT *); static gpre_rel* par_base_table(gpre_req*, const gpre_rel*, const TEXT*); static gpre_nod* par_case(gpre_req*); static gpre_nod* par_collate(gpre_req*, gpre_nod*); static gpre_nod* par_in(gpre_req*, gpre_nod*); static gpre_ctx* par_joined_relation(gpre_req*); static gpre_ctx* par_join_clause(gpre_req*, gpre_ctx*); static nod_t par_join_type(); static gpre_nod* par_multiply(gpre_req*, bool, USHORT *, bool *); static gpre_nod* par_not(gpre_req*, USHORT *); static gpre_nod* par_nullif(gpre_req*); static void par_order(gpre_req*, gpre_rse*, bool, bool); static gpre_nod* par_plan(gpre_req*); static gpre_nod* par_plan_item(gpre_req*, bool, USHORT *, bool *); static gpre_nod* par_primitive_value(gpre_req*, bool, USHORT *, bool *); static gpre_nod* par_relational(gpre_req*, USHORT *); static gpre_rse* par_rse(gpre_req*, gpre_nod*, bool); static gpre_rse* par_select(gpre_req*, gpre_rse*); static gpre_nod* par_stat(gpre_req*); static gpre_nod* par_subscript(gpre_req*); static gpre_nod* par_substring(gpre_req*); static void par_terminating_parens(USHORT *, USHORT *); static gpre_nod* par_udf(gpre_req*); static gpre_nod* par_udf_or_field(gpre_req*, bool); static gpre_nod* par_udf_or_field_with_collate(gpre_req*, bool, USHORT *, bool *); static void par_update(gpre_rse*, bool, bool); static gpre_nod* post_fields(gpre_nod*, map*); static gpre_nod* post_map(gpre_nod*, map*); static gpre_nod* post_select_list(gpre_nod*, map*); static void pop_scope(gpre_req*, scope*); static void push_scope(gpre_req*, scope*); static gpre_fld* resolve(gpre_nod*, gpre_ctx*, gpre_ctx**, act**); static bool resolve_fields(gpre_nod*& fields, gpre_rse* selection); static gpre_ctx* resolve_asterisk(const tok*, gpre_rse*); static void set_ref(gpre_nod*, gpre_fld*); static char* upcase_string(const char*); static bool validate_references(const gpre_nod*, const gpre_nod*); struct ops { nod_t rel_op; kwwords_t rel_kw; nod_t rel_negation; }; static const ops rel_ops[] = { { nod_eq, KW_EQ, nod_ne }, { nod_eq, KW_EQUALS, nod_ne }, { nod_ne, KW_NE, nod_eq }, { nod_gt, KW_GT, nod_le }, { nod_ge, KW_GE, nod_lt }, { nod_le, KW_LE, nod_gt }, { nod_lt, KW_LT, nod_ge }, { nod_containing, KW_CONTAINING, nod_any }, { nod_starting, KW_STARTING, nod_any }, { nod_matches, KW_MATCHES, nod_any }, { nod_any, KW_none, nod_any }, { nod_ansi_any, KW_none, nod_ansi_any }, { nod_ansi_all, KW_none, nod_ansi_all } }; #ifdef NOT_USED_OR_REPLACED static const ops scalar_stat_ops[] = { { nod_count, KW_COUNT, nod_any }, { nod_max, KW_MAX, nod_any }, { nod_min, KW_MIN, nod_any }, { nod_total, KW_TOTAL, nod_any }, { nod_total, KW_SUM, nod_any }, { nod_average, KW_AVERAGE, nod_any }, { nod_via, KW_none, nod_any} }; #endif static const ops stat_ops[] = { { nod_agg_count, KW_COUNT, nod_any }, { nod_agg_max, KW_MAX, nod_any }, { nod_agg_min, KW_MIN, nod_any }, { nod_agg_total, KW_TOTAL, nod_any }, { nod_agg_total, KW_SUM, nod_any }, { nod_agg_average, KW_AVERAGE, nod_any }, { nod_any, KW_none, nod_any }, { nod_ansi_any, KW_none, nod_ansi_any }, { nod_ansi_all, KW_none, nod_ansi_all } }; static const nod_t relationals[] = { nod_eq, nod_ne, nod_gt, nod_ge, nod_le, nod_lt, nod_containing, nod_starting, nod_matches, nod_any, nod_missing, nod_equiv, nod_between, nod_like, nod_and, nod_or, nod_not, nod_ansi_any, nod_ansi_all, nod_nothing }; //____________________________________________________________ // // Parse an OR boolean expression. // gpre_nod* SQE_boolean( gpre_req* request, USHORT* paren_count) { USHORT local_count; assert_IS_REQ(request); if (!paren_count) { local_count = 0; paren_count = &local_count; } gpre_nod* expr1 = par_and(request, paren_count); if (!MSC_match(KW_OR) && !MSC_match(KW_OR1)) { par_terminating_parens(paren_count, &local_count); return expr1; } expr1 = MSC_binary(nod_or, expr1, SQE_boolean(request, paren_count)); par_terminating_parens(paren_count, &local_count); return expr1; } //____________________________________________________________ // // Parse a reference to a relation name // and generate a context for it. // gpre_ctx* SQE_context(gpre_req* request) { SCHAR r_name[NAME_SIZE], db_name[NAME_SIZE], owner_name[NAME_SIZE]; SCHAR s[ERROR_LENGTH]; assert_IS_REQ(request); gpre_ctx* context = MSC_context(request); SQL_relation_name(r_name, db_name, owner_name); if (!(context->ctx_relation = SQL_relation(request, r_name, db_name, owner_name, false))) { // check for a procedure gpre_prc* procedure = context->ctx_procedure = SQL_procedure(request, r_name, db_name, owner_name, false); if (procedure) { if (procedure->prc_inputs) { if (!MSC_match(KW_LEFT_PAREN)) CPR_s_error("( )"); // parse input references context->ctx_prc_inputs = SQE_list(SQE_value, request, false); USHORT local_count = 1; par_terminating_parens(&local_count, &local_count); if (procedure->prc_in_count != context->ctx_prc_inputs->nod_count) { PAR_error("count of input values doesn't match count of parameters"); } gpre_nod** input = context->ctx_prc_inputs->nod_arg; for (gpre_fld* field = procedure->prc_inputs; field; input++, field = field->fld_next) { SQE_post_field(*input, field); } } } else { if (owner_name[0]) sprintf(s, "table %s.%s not defined", owner_name, r_name); else sprintf(s, "table %s not defined", r_name); PAR_error(s); } } // If the next token is recognized as a keyword, it can't be a SQL "alias". // It may, however, be an "end of line" token. If so, trade it in on the // next "real" token. gpre_sym* symbol = gpreGlob.token_global.tok_symbol; if (symbol && symbol->sym_type == SYM_keyword) { if (!gpreGlob.token_global.tok_length) CPR_token(); return context; } // we have what we assume to be an alias; check to make sure that // it does not conflict with any relation, procedure or context names // at the same scoping level in this query gpre_ctx* conflict; for (conflict = request->req_contexts; conflict; conflict = conflict->ctx_next) { if ((symbol = conflict->ctx_symbol) && (symbol->sym_type == SYM_relation || symbol->sym_type == SYM_context || symbol->sym_type == SYM_procedure) && !strcmp(symbol->sym_string, gpreGlob.token_global.tok_string) && conflict->ctx_scope_level == request->req_scope_level) { break; } } if (conflict) { const char* error_type; switch (symbol->sym_type) { case SYM_relation: error_type = "table"; break; case SYM_procedure: error_type = "procedure"; break; default: error_type = "context"; } fb_utils::snprintf(s, sizeof(s), "context %s conflicts with a %s in the same statement", gpreGlob.token_global.tok_string, error_type); PAR_error(s); } symbol = MSC_symbol(SYM_context, gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, 0); symbol->sym_object = context; context->ctx_symbol = symbol; context->ctx_alias = symbol->sym_name; HSH_insert(symbol); PAR_get_token(); return context; } //____________________________________________________________ // // Parse an item is a select list. This is particularly nasty // since neither the relations nor context variables have been // processed yet. So, rather than generating a simple field // reference, make up a more or less dummy block containing // a pointer to a field system and possible a qualifier symbol. // this will be turned into a reference later. // gpre_nod* SQE_field(gpre_req* request, bool aster_ok) { gpre_req* slice_req; assert_IS_REQ(request); gpre_lls* upper_dim = NULL; gpre_lls* lower_dim = NULL; tok hold_token; hold_token.tok_type = tok_t(0); if (aster_ok && MSC_match(KW_ASTERISK)) { gpre_nod* node = MSC_node(nod_asterisk, 1); return node; } // if the token isn't an identifier, complain SQL_resolve_identifier("", NULL, NAME_SIZE); // For domains we can't be resolving tokens to field names // in the CHECK constraint. TEXT s[ERROR_LENGTH]; act* action; if (request && request->req_type == REQ_ddl && (action = request->req_actions) && (action->act_type == ACT_create_domain || action->act_type == ACT_alter_domain)) { fb_utils::snprintf(s, sizeof(s), "Illegal use of identifier: %s in domain constraint", gpreGlob.token_global.tok_string); PAR_error(s); } // Note: We *always* want to make a deferred name block - to handle // scoping of alias names in subselects properly, when we haven't // seen the list of relations (& aliases for them). This occurs // during the select list, but by the time we see the having, group, // order, or where we do know all aliases and should be able to // precisely match. // Note that the case of request == NULL should never // occur, and request->req_contexts == NULL should only // occur for the very first select list in a request. // 1994-October-03 David Schnepper // if the request is null, make a deferred name block if (!request || !request->req_contexts || request->req_in_select_list) { gpre_nod* node = MSC_node(nod_deferred, 3); node->nod_count = 0; tok* f_token = (tok*) MSC_alloc(TOK_LEN); node->nod_arg[0] = (gpre_nod*) f_token; f_token->tok_length = gpreGlob.token_global.tok_length; SQL_resolve_identifier("", f_token->tok_string, f_token->tok_length + 1); CPR_token(); if (MSC_match(KW_DOT)) { if (gpreGlob.token_global.tok_keyword == KW_ASTERISK) { if (aster_ok) node->nod_type = nod_asterisk; else PAR_error("* not allowed"); } else { node->nod_arg[1] = node->nod_arg[0]; f_token = (tok*) MSC_alloc(TOK_LEN); node->nod_arg[0] = (gpre_nod*) f_token; f_token->tok_length = gpreGlob.token_global.tok_length; SQL_resolve_identifier("", f_token->tok_string, f_token->tok_length + 1); } CPR_token(); } if (MSC_match(KW_L_BRCKET)) { // We have a complete array or an array slice here if (!MSC_match(KW_R_BRCKET)) { slice_req = MSC_request(REQ_slice); int count = 0; do { count++; gpre_nod* tail = par_subscript(slice_req); MSC_push(tail, &lower_dim); if (MSC_match(KW_COLON)) { //if (!MSC_match (KW_DOT)) // CPR_s_error (""); tail = par_subscript(slice_req); MSC_push(tail, &upper_dim); } else MSC_push(tail, &upper_dim); } while (MSC_match(KW_COMMA)); if (!MSC_match(KW_R_BRCKET)) CPR_s_error(""); slc* slice = (slc*) MSC_alloc(SLC_LEN(count)); slice_req->req_slice = slice; slc::slc_repeat* tail_ptr = &slice->slc_rpt[count]; slice->slc_dimensions = count; slice->slc_parent_request = request; while (lower_dim) { --tail_ptr; tail_ptr->slc_lower = MSC_pop(&lower_dim); tail_ptr->slc_upper = MSC_pop(&upper_dim); } node->nod_arg[2] = (gpre_nod*) slice_req; // added this to assign the correct nod_count // The nod type is converted to nod_field in SQE_resolve() // The nod_count is check to confirm if the array slice // has been initialized in cmd.cpp node->nod_count = 3; } else { slice_req = (gpre_req*) MSC_alloc(REQ_LEN); slice_req->req_type = REQ_slice; node->nod_arg[2] = (gpre_nod*) slice_req; } } return node; } gpre_ctx* context; ref* reference = (ref*) MSC_alloc(REF_LEN); gpre_nod* node = MSC_unary(nod_field, (gpre_nod*) reference); gpre_sym* symbol = gpreGlob.token_global.tok_symbol; if (symbol) { // if there is a homonym which is a context, use the context; // otherwise we may match with a relation or procedure which // is not in the request, resulting in a bogus error if (symbol->sym_type != SYM_field) { for (gpre_sym* temp_symbol = symbol; temp_symbol; temp_symbol = temp_symbol->sym_homonym) { if (temp_symbol->sym_type == SYM_context) { symbol = temp_symbol; break; } if (temp_symbol->sym_type == SYM_relation) { symbol = temp_symbol; continue; } if (temp_symbol->sym_type == SYM_procedure) { if (symbol->sym_type != SYM_relation) symbol = temp_symbol; continue; } } if (symbol->sym_type == SYM_context) { context = symbol->sym_object; CPR_token(); if (!MSC_match(KW_DOT)) CPR_s_error(" in qualified column"); if (context->ctx_request != request) PAR_error("context not part of this request"); SQL_resolve_identifier("", NULL, NAME_SIZE); if (!(reference->ref_field = MET_context_field(context, gpreGlob.token_global.tok_string))) { sprintf(s, "column \"%s\" not in context", gpreGlob.token_global.tok_string); PAR_error(s); } if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) { const USHORT field_dtype = reference->ref_field->fld_dtype; if (dtype_sql_date == field_dtype || dtype_sql_time == field_dtype || dtype_int64 == field_dtype) { SQL_dialect1_bad_type(field_dtype); } } reference->ref_context = context; CPR_token(); return node; } if (symbol->sym_type == SYM_relation) { const gpre_rel* relation = (gpre_rel*) symbol->sym_object; if (relation->rel_database != request->req_database) PAR_error("table not in appropriate database"); CPR_token(); // if we do not see a KW_DOT, perhaps what we think is a relation // is actually a column with the same name as the relation in // the HSH table. if so...skip down to the end of the routine // and find out if we do have such a column. but first, since // we just did a CPR_token, we have to move prior_token into // current token, and hold the current token for later. if (!MSC_match(KW_DOT)) { hold_token = gpreGlob.token_global; gpreGlob.token_global = gpreGlob.prior_token; gpreGlob.token_global.tok_symbol = 0; } else { // We've got the column name. resolve it SQL_resolve_identifier("", NULL, NAME_SIZE); for (context = request->req_contexts; context; context = context->ctx_next) { if (context->ctx_relation == relation && (reference->ref_field = MET_field(context->ctx_relation, gpreGlob.token_global.tok_string))) { if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) { const USHORT field_dtype = reference->ref_field->fld_dtype; if (dtype_sql_date == field_dtype || dtype_sql_time == field_dtype || dtype_int64 == field_dtype) { SQL_dialect1_bad_type(field_dtype); } } reference->ref_context = context; CPR_token(); if (reference->ref_field->fld_array_info) { node = EXP_array(request, reference->ref_field, true, true); node->nod_arg[0] = (gpre_nod*) reference; } return node; } } fb_utils::snprintf(s, sizeof(s), "column \"%s\" not in context", gpreGlob.token_global.tok_string); PAR_error(s); } } else if (symbol->sym_type == SYM_procedure) { const gpre_prc* procedure = (gpre_prc*) symbol->sym_object; if (procedure->prc_database != request->req_database) PAR_error("procedure not in appropriate database"); CPR_token(); if (!MSC_match(KW_DOT)) CPR_s_error(" in qualified column"); SQL_resolve_identifier("", NULL, NAME_SIZE); for (context = request->req_contexts; context; context = context->ctx_next) { if (context->ctx_procedure == procedure && (reference->ref_field = MET_context_field(context, gpreGlob.token_global.tok_string))) { if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) { const USHORT field_dtype = reference->ref_field->fld_dtype; if (dtype_sql_date == field_dtype || dtype_sql_time == field_dtype || dtype_int64 == field_dtype) { SQL_dialect1_bad_type(field_dtype); } } reference->ref_context = context; if (reference->ref_field->fld_array_info) { node = EXP_array(request, reference->ref_field, true, true); node->nod_arg[0] = (gpre_nod*) reference; } CPR_token(); return node; } } fb_utils::snprintf(s, sizeof(s), "column \"%s\" not in context", gpreGlob.token_global.tok_string); PAR_error(s); } } } // Hmmm. So it wasn't a qualified field. Try any field. SQL_resolve_identifier("", NULL, NAME_SIZE); for (context = request->req_contexts; context; context = context->ctx_next) { if (reference->ref_field = MET_context_field(context, gpreGlob.token_global.tok_string)) { if (SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) { const USHORT field_dtype = reference->ref_field->fld_dtype; if (dtype_sql_date == field_dtype || dtype_sql_time == field_dtype || dtype_int64 == field_dtype) { SQL_dialect1_bad_type(field_dtype); } } reference->ref_context = context; // if we skipped down from the SYM_relation case above, we need to // switch token and prior_token back to their original values to // continue. if (hold_token.tok_type != 0) { gpreGlob.prior_token = gpreGlob.token_global; gpreGlob.token_global = hold_token; } else CPR_token(); if (reference->ref_field->fld_array_info) { node = EXP_array(request, reference->ref_field, true, true); node->nod_arg[0] = (gpre_nod*) reference; } if (request->req_map) return post_map(node, request->req_map); return node; } } CPR_s_error(""); return NULL; // silence compiler } //____________________________________________________________ // // Parse a list of "things", separated by commas. Return the // whole mess in a list node. // gpre_nod* SQE_list(pfn_SQE_list_cb routine, gpre_req* request, bool aster_ok) { assert_IS_REQ(request); gpre_lls* stack = NULL; int count = 0; do { count++; MSC_push((*routine) (request, aster_ok, NULL, NULL), &stack); } while (MSC_match(KW_COMMA)); gpre_nod* list = MSC_node(nod_list, (SSHORT) count); gpre_nod** ptr = &list->nod_arg[count]; while (stack) *--ptr = (gpre_nod*) MSC_pop(&stack); return list; } //____________________________________________________________ // // Parse procedure input parameters which are constants or // host variable reference and, perhaps, a missing // flag reference, which may be prefaced by the noiseword, // "INDICATOR". // ref* SQE_parameter(gpre_req* request) { ref* reference; SCHAR* string; assert_IS_REQ(request); if (gpreGlob.token_global.tok_type == tok_number) { reference = (ref*) MSC_alloc(REF_LEN); string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 1); MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string); reference->ref_value = string; reference->ref_flags |= REF_literal; CPR_token(); return reference; } if ((isQuoted(gpreGlob.token_global.tok_type) && gpreGlob.sw_sql_dialect == 1) || gpreGlob.token_global.tok_type == tok_sglquoted) { // Since we have stripped the quotes, it is time now to put it back // so that the host language will interpret it correctly as a string // literal. reference = (ref*) MSC_alloc(REF_LEN); string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 3); string[0] = '\"'; MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string + 1); string[gpreGlob.token_global.tok_length + 1] = '\"'; string[gpreGlob.token_global.tok_length + 2] = 0; reference->ref_value = string; reference->ref_flags |= REF_literal; CPR_token(); return reference; } if (gpreGlob.token_global.tok_keyword == KW_PLUS || gpreGlob.token_global.tok_keyword == KW_MINUS) { int sign = 0; if (gpreGlob.token_global.tok_keyword == KW_MINUS) sign = 1; CPR_token(); if (gpreGlob.token_global.tok_type != tok_number) CPR_s_error(" or "); reference = (ref*) MSC_alloc(REF_LEN); char* s = string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 1 + sign); if (sign) *s++ = '-'; MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, s); reference->ref_value = string; reference->ref_flags |= REF_literal; CPR_token(); return reference; } if (!MSC_match(KW_COLON)) CPR_s_error(" or "); if (gpreGlob.token_global.tok_type != tok_ident) CPR_s_error(" or "); reference = (ref*) MSC_alloc(REF_LEN); // This loop is a waste of time because the flag SYM_variable is never activated for (gpre_sym* symbol = gpreGlob.token_global.tok_symbol; symbol; symbol = symbol->sym_homonym) { if (symbol->sym_type == SYM_variable) { reference->ref_field = (gpre_fld*) symbol->sym_object; break; } } reference->ref_value = PAR_native_value(false, false); MSC_match(KW_INDICATOR); if (MSC_match(KW_COLON)) reference->ref_null_value = PAR_native_value(false, false); return reference; } //____________________________________________________________ // // Given an expression node, for values that don't have a // field, post the given field. // Procedure called from EXP_array to post the "subscript field". // void SQE_post_field( gpre_nod* input, gpre_fld* field) { if (!input || !field) return; assert_IS_NOD(input); switch (input->nod_type) { case nod_value: { ref* reference = (ref*) input->nod_arg[0]; if (!reference->ref_field) { // We're guessing that hostvar reference->ref_value matches // the datatype of field->fld_dtype reference->ref_field = field; } return; } case nod_field: case nod_literal: case nod_deferred: case nod_array: return; case nod_map_ref: { mel* element = (mel*) input->nod_arg[0]; gpre_nod* node = element->mel_expr; SQE_post_field(node, field); return; } default: { gpre_nod** ptr = input->nod_arg; for (const gpre_nod* const* const end = ptr + input->nod_count; ptr < end; ptr++) { SQE_post_field(*ptr, field); } return; } } } //____________________________________________________________ // // Post an external reference to a request. If the expression // in question already exists, re-use it. If there isn't a field, // generate a pseudo-field to hold datatype information. If there // isn't a context, well, there isn't a context. // ref* SQE_post_reference(gpre_req* request, gpre_fld* field, gpre_ctx* context, gpre_nod* node) { ref* reference; assert_IS_REQ(request); assert_IS_NOD(node); // If the beast is already a field reference, get component parts if (node && node->nod_type == nod_field) { reference = (ref*) node->nod_arg[0]; field = reference->ref_field; context = reference->ref_context; } // See if there is already a reference to this guy. If so, return it. for (reference = request->req_references; reference; reference = reference->ref_next) { if ((reference->ref_expr && compare_expr(node, reference->ref_expr)) || (!reference->ref_expr && field == reference->ref_field && context == reference->ref_context)) { return reference; } } // If there isn't a field given, make one up if (!field) { field = (gpre_fld*) MSC_alloc(FLD_LEN); CME_get_dtype(node, field); if (field->fld_dtype && (field->fld_dtype <= dtype_any_text)) field->fld_flags |= FLD_text; } // No reference -- make one reference = (ref*) MSC_alloc(REF_LEN); reference->ref_context = context; reference->ref_field = field; reference->ref_expr = node; reference->ref_next = request->req_references; request->req_references = reference; return reference; } //____________________________________________________________ // // Resolve a kludgy field node build by par_s_item into // a bona fide field reference. If a request // is supplied, resolve the reference to any context available // in the request. Otherwise resolve the field to a given // record selection expression. // // If the expression contains a global aggregate, return true, // otherwise false. // bool SQE_resolve(gpre_nod** node_ptr, gpre_req* request, gpre_rse* selection) { bool result = false; act* slice_action = 0; gpre_nod* node = *node_ptr; assert_IS_REQ(request); assert_IS_NOD(node); switch (node->nod_type) { case nod_plus: case nod_minus: case nod_times: case nod_divide: case nod_negate: case nod_and: case nod_or: case nod_not: case nod_eq: case nod_ne: case nod_ge: case nod_gt: case nod_le: case nod_lt: case nod_containing: case nod_like: case nod_between: case nod_starting: case nod_upcase: case nod_lowcase: case nod_concatenate: case nod_cast: case nod_case: case nod_case1: case nod_substring: { gpre_nod** ptr = node->nod_arg; const gpre_nod* const* const end = ptr + node->nod_count; for (; ptr < end; ptr++) result |= SQE_resolve(ptr, request, selection); return result; } case nod_agg_max: case nod_agg_min: case nod_agg_total: case nod_agg_average: case nod_agg_count: if (node->nod_arg[0]) { SQE_resolve(&node->nod_arg[0], request, selection); gpre_nod* node_arg = node->nod_arg[0]; const ref* reference = (ref*) node_arg->nod_arg[0]; if (node_arg->nod_type == nod_field && reference && reference->ref_field && reference->ref_field->fld_array_info) { PAR_error("Array columns not permitted in aggregate functions"); } } return true; case nod_udf: if (node->nod_arg[0]) { gpre_nod** ptr = node->nod_arg[0]->nod_arg; const gpre_nod* const* const end = ptr + node->nod_arg[0]->nod_count; for (; ptr < end; ptr++) result |= SQE_resolve(ptr, request, selection); } return result; case nod_gen_id: return SQE_resolve(&node->nod_arg[0], request, selection); // Begin date/time/timestamp support case nod_extract: result |= SQE_resolve(&node->nod_arg[1], request, selection); return result; // End date/time/timestamp support case nod_coalesce: for (int i = 0; i < node->nod_count; i++) result |= SQE_resolve(&node->nod_arg[0]->nod_arg[i], request, selection); return result; case nod_deferred: break; default: return false; } tok* f_token = (tok*) node->nod_arg[0]; f_token->tok_symbol = HSH_lookup(f_token->tok_string); tok* q_token = (tok*) node->nod_arg[1]; if (q_token) q_token->tok_symbol = HSH_lookup(q_token->tok_string); gpre_fld* field = NULL; gpre_ctx* context = NULL; if (request) { for (context = request->req_contexts; context; context = context->ctx_next) { if (!context->ctx_stream && (field = resolve(node, context, 0, &slice_action))) break; } } else { for (SSHORT i = 0; i < selection->rse_count; i++) { if (field = resolve(node, selection->rse_context[i], &context, &slice_action)) break; } } if (!field) { SCHAR s[ERROR_LENGTH]; if (q_token) fb_utils::snprintf(s, sizeof(s), "column \"%s.%s\" cannot be resolved", q_token->tok_string, f_token->tok_string); else fb_utils::snprintf(s, sizeof(s), "column \"%s\" cannot be resolved", f_token->tok_string); PAR_error(s); } // Make sure that a dialect-1 program isn't trying to select a // dialect-3-only field type. if ((SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) && (dtype_sql_date == field->fld_dtype || dtype_sql_time == field->fld_dtype || dtype_int64 == field->fld_dtype)) { SQL_dialect1_bad_type(field->fld_dtype); } ref* reference = (ref*) MSC_alloc(REF_LEN); reference->ref_field = field; reference->ref_context = context; reference->ref_slice = (slc*) slice_action; *node_ptr = MSC_unary(nod_field, (gpre_nod*) reference); return false; } //____________________________________________________________ // // Parse a SELECT (sans keyword) expression. // gpre_rse* SQE_select(gpre_req* request, bool view_flag) { gpre_lls* context_stack = NULL; gpre_ctx* context = 0; bool have_union = false; assert_IS_REQ(request); map* const old_map = request->req_map; // Get components of union. Most likely there isn't one, so this is // probably wasted work. gpre_rse* select = NULL; gpre_rse* rse1 = NULL; select = rse1 = par_select(request, NULL); // "Look for ... the UNION label ... " while (MSC_match(KW_UNION)) { have_union = true; const bool union_all = MSC_match(KW_ALL); if (!MSC_match(KW_SELECT)) CPR_s_error("SELECT"); MSC_push((gpre_nod*) request->req_contexts, &context_stack); request->req_contexts = NULL; request->req_map = NULL; gpre_rse* rse2 = par_select(request, rse1); // We've got a bona fide union. Make a union node to hold sub-rse // and then a new rse to point to it. select = (gpre_rse*) MSC_alloc(RSE_LEN(1)); select->rse_context[0] = context = MSC_context(request); gpre_nod* node = MSC_node(nod_union, 2); select->rse_union = node; node->nod_arg[0] = (gpre_nod*) rse1; node->nod_arg[1] = (gpre_nod*) rse2; map* new_map = (map*) MSC_alloc(sizeof(map)); rse1->rse_map = new_map; new_map->map_context = context; select->rse_fields = post_select_list(rse1->rse_fields, new_map); rse2->rse_map = new_map = (map*) MSC_alloc(sizeof(map)); new_map->map_context = context; post_select_list(rse2->rse_fields, new_map); select->rse_into = rse1->rse_into; if (!union_all) select->rse_reduced = select->rse_fields; // Result of this UNION might be the left side of the NEXT UNION rse1 = select; } // Restore the context lists that were forgotten // holds the most recently allocated context, which is // already linked into the request block while (context_stack) { while (context->ctx_next) context = context->ctx_next; context->ctx_next = (gpre_ctx*) MSC_pop(&context_stack); } // Pick up any dangling ORDER clause ++request->req_in_order_by_clause; par_order(request, select, have_union, view_flag); --request->req_in_order_by_clause; par_update(select, have_union, view_flag); request->req_map = old_map; return select; } //____________________________________________________________ // // Parse either of the low precedence operators + and -. // gpre_nod* SQE_value(gpre_req* request, bool aster_ok, USHORT* paren_count, bool* bool_flag) { USHORT local_count; bool local_flag; assert_IS_REQ(request); if (!paren_count) { local_count = 0; paren_count = &local_count; } if (!bool_flag) { local_flag = false; bool_flag = &local_flag; } MSC_match(KW_PLUS); gpre_nod* node = par_multiply(request, aster_ok, paren_count, bool_flag); assert_IS_NOD(node); if (node->nod_type == nod_asterisk) { par_terminating_parens(paren_count, &local_count); return node; } nod_t nod_type; while (true) { if (MSC_match(KW_PLUS)) nod_type = nod_plus; else if (MSC_match(KW_MINUS)) nod_type = nod_minus; else if (MSC_match(KW_OR1)) nod_type = nod_concatenate; else { par_terminating_parens(paren_count, &local_count); return node; } node = MSC_binary(nod_type, node, par_multiply(request, false, paren_count, bool_flag)); } } //____________________________________________________________ // // Parse either a literal NULL expression or a value // expression. // gpre_nod* SQE_value_or_null(gpre_req* request, bool aster_ok, USHORT* paren_count, bool* bool_flag) { if (MSC_match(KW_NULL)) return MSC_node(nod_null, 0); return SQE_value(request, aster_ok, paren_count, bool_flag); } //____________________________________________________________ // // Parse host variable reference and, perhaps, a missing // flag reference, which may be prefaced by the noiseword, // "INDICATOR". // gpre_nod* SQE_variable(gpre_req* request, bool /*aster_ok*/, USHORT* /*paren_count*/, bool* /*bool_flag*/) { assert_IS_REQ(request); if (!MSC_match(KW_COLON)) CPR_s_error(""); if (isQuoted(gpreGlob.token_global.tok_type)) CPR_s_error(""); ref* reference = (ref*) MSC_alloc(REF_LEN); // This loop is a waste of time because the flag SYM_variable is never activated for (gpre_sym* symbol = gpreGlob.token_global.tok_symbol; symbol; symbol = symbol->sym_homonym) { if (symbol->sym_type == SYM_variable) { reference->ref_field = (gpre_fld*) symbol->sym_object; break; } } reference->ref_value = PAR_native_value(false, false); MSC_match(KW_INDICATOR); if (MSC_match(KW_COLON)) reference->ref_null_value = PAR_native_value(false, false); return (gpre_nod*) reference; } //____________________________________________________________ // // Compare two expressions symbollically. If they're the same, // return true, otherwise false. // static bool compare_expr(const gpre_nod* node1, const gpre_nod* node2) { assert_IS_NOD(node1); assert_IS_NOD(node2); if (node1->nod_type != node2->nod_type) return false; switch (node1->nod_type) { case nod_field: { const ref* ref1 = (ref*) node1->nod_arg[0]; const ref* ref2 = (ref*) node2->nod_arg[0]; return ref1->ref_context == ref2->ref_context && ref1->ref_field == ref2->ref_field && ref1->ref_master == ref2->ref_master; } case nod_map_ref: return node1->nod_arg[0] == node2->nod_arg[0]; case nod_udf: case nod_gen_id: return node1->nod_arg[0] == node2->nod_arg[0] && node1->nod_arg[1] == node2->nod_arg[1]; default: return false; } } //____________________________________________________________ // // Copy a field list for a SELECT against an artificial context. // static gpre_nod* copy_fields( gpre_nod* fields, map* fields_map) { assert_IS_NOD(fields); gpre_nod* list = MSC_node(nod_list, fields->nod_count); for (USHORT i = 0; i < fields->nod_count; i++) list->nod_arg[i] = post_fields(fields->nod_arg[i], fields_map); return list; } //____________________________________________________________ // // Expand an '*' in a field list to the corresponding fields. // static gpre_nod* explode_asterisk( gpre_nod* fields, int n, gpre_rse* selection) { TEXT s[ERROR_LENGTH]; assert_IS_NOD(fields); gpre_nod* node = fields->nod_arg[n]; tok* q_token = (tok*) node->nod_arg[0]; if (q_token) { // expand for single relation gpre_ctx* context = resolve_asterisk(q_token, selection); if (context) fields = merge_fields(fields, MET_fields(context), n, true); else { sprintf(s, "columns \"%s.*\" cannot be resolved", q_token->tok_string); PAR_error(s); } } else { // expand for all relations in context list fields = explode_asterisk_all(fields, n, selection, true); } return fields; } //____________________________________________________________ // // Expand an '*' for all relations // in the context list. // static gpre_nod* explode_asterisk_all(gpre_nod* fields, int n, gpre_rse* selection, bool replace) { assert_IS_NOD(fields); for (int i = 0; i < selection->rse_count; i++) { gpre_ctx* context = selection->rse_context[i]; const int old_count = fields->nod_count; if (context->ctx_stream) fields = explode_asterisk_all(fields, n, context->ctx_stream, replace); else fields = merge_fields(fields, MET_fields(context), n, replace); n += fields->nod_count - old_count; replace = false; } return fields; } //____________________________________________________________ // // Get an element of an expression to act as a reference // field for determining the data type of a host variable. // static gpre_fld* get_ref( gpre_nod* expr) { gpre_fld* field; assert_IS_NOD(expr); if (expr->nod_type == nod_via || expr->nod_type == nod_cast) { field = (gpre_fld*) MSC_alloc(FLD_LEN); CME_get_dtype(expr, field); if (field->fld_dtype && (field->fld_dtype <= dtype_any_text)) field->fld_flags |= FLD_text; return field; } ref* reference; switch (expr->nod_type) { case nod_field: reference = (ref*) expr->nod_arg[0]; return reference->ref_field; case nod_array: reference = (ref*) expr->nod_arg[0]; return reference->ref_field->fld_array; case nod_agg_count: case nod_agg_max: case nod_agg_min: case nod_agg_total: case nod_agg_average: case nod_plus: case nod_minus: case nod_times: case nod_divide: case nod_negate: case nod_upcase: case nod_lowcase: case nod_concatenate: { gpre_nod** ptr = expr->nod_arg; for (const gpre_nod* const* const end = ptr + expr->nod_count; ptr < end; ptr++) { if (field = get_ref(*ptr)) return field; } break; } // Begin date/time/timestamp support case nod_extract: if (field = get_ref(expr->nod_arg[1])) return field; break; // End date/time/timestamp support case nod_map_ref: { mel* element = (mel*) expr->nod_arg[0]; gpre_nod* node = element->mel_expr; return get_ref(node); } } return 0; } //____________________________________________________________ // // Finish off processing an implicit ANY clause. Assume that the word // "select" has already been recognized. If the outer thing is a group // by, so we're looking at a "having", re-resolve the input value to the // map. // static gpre_nod* implicit_any(gpre_req* request, gpre_nod* value, nod_t comparison, nod_t any_all) { gpre_nod* node; scope previous_scope; assert_IS_REQ(request); assert_IS_NOD(value); gpre_ctx* original = request->req_contexts; request->req_in_subselect++; push_scope(request, &previous_scope); if (!(original->ctx_relation || original->ctx_procedure) && request->req_map) value = post_fields(value, request->req_map); // Handle the ALL and DISTINCT options const bool distinct = (!MSC_match(KW_ALL) && MSC_match(KW_DISTINCT)); request->req_in_select_list++; gpre_nod* value2 = SQE_value(request, false, NULL, NULL); request->req_in_select_list--; gpre_nod* field_list = MSC_node(nod_list, 1); field_list->nod_arg[0] = value2; gpre_rse* selection = par_rse(request, field_list, distinct); value2 = selection->rse_fields->nod_arg[0]; gpre_rse* sub = selection->rse_aggregate; if (sub) { if (validate_references(value2, sub->rse_group_by)) PAR_error("simple column reference not allowed in aggregate context"); if (sub->rse_group_by) { node = MSC_binary(comparison, value, value2); pair(node->nod_arg[0], node->nod_arg[1]); selection->rse_boolean = merge(selection->rse_boolean, node); if (any_all == nod_ansi_all) node = MSC_node(nod_ansi_all, 1); else node = MSC_node(nod_ansi_any, 1); node->nod_count = 0; node->nod_arg[0] = (gpre_nod*) selection; } else { gpre_nod* node2 = MSC_node(nod_via, 3); node2->nod_count = 0; node2->nod_arg[0] = (gpre_nod*) selection; node2->nod_arg[2] = MSC_node(nod_null, 0); node2->nod_arg[1] = value2; node = MSC_binary(comparison, value, node2); pair(node->nod_arg[0], node->nod_arg[1]); } } else { node = MSC_binary(comparison, value, value2); pair(node->nod_arg[0], node->nod_arg[1]); selection->rse_boolean = merge(selection->rse_boolean, node); if (any_all == nod_ansi_all) node = MSC_node(nod_ansi_all, 1); else node = MSC_node(nod_ansi_any, 1); node->nod_count = 0; node->nod_arg[0] = (gpre_nod*) selection; } EXP_rse_cleanup(selection); pop_scope(request, &previous_scope); request->req_in_subselect--; return node; } //____________________________________________________________ // // Merge two (possibly null) booleans into a single conjunct. // static gpre_nod* merge( gpre_nod* expr1, gpre_nod* expr2) { if (!expr1) return expr2; if (!expr2) return expr1; assert_IS_NOD(expr1); assert_IS_NOD(expr1); return MSC_binary(nod_and, expr1, expr2); } //____________________________________________________________ // // Merge 2 field lists // 2nd list is added over nth entry in 1st list // if replace is true, otherwise it is added // after the nth entry. // static gpre_nod* merge_fields(gpre_nod* fields_1, gpre_nod* fields_2, int n, bool replace) { assert_IS_NOD(fields_1); assert_IS_NOD(fields_2); int count = fields_1->nod_count + fields_2->nod_count; if (replace) count--; gpre_nod* fields = MSC_node(nod_list, (SSHORT) count); count = n; if (!replace) count++; for (int i = 0; i < count; i++) fields->nod_arg[i] = fields_1->nod_arg[i]; for (int i = 0; i < fields_2->nod_count; i++) fields->nod_arg[i + count] = fields_2->nod_arg[i]; int offset = 0; if (replace) { count++; offset = 1; } for (int i = count; i < fields_1->nod_count; i++) fields->nod_arg[i + fields_2->nod_count - offset] = fields_1->nod_arg[i]; return fields; } //____________________________________________________________ // // Construct negation of expression. // static gpre_nod* negate( gpre_nod* expr) { assert_IS_NOD(expr); return MSC_unary(nod_not, expr); } //____________________________________________________________ // // Given two value expressions associated in a relational // expression, see if one is a field reference and the other // is a host language variable.. If so, match the field to the // host language variable. // static void pair( gpre_nod* expr1, gpre_nod* expr2) { assert_IS_NOD(expr1); assert_IS_NOD(expr2); // Verify that an array field without subscripts is not // being used inappropriately if (expr1 && expr2) { if (expr1->nod_type == nod_array && !expr1->nod_arg[1]) PAR_error("Invalid array column reference"); if (expr2->nod_type == nod_array && !expr2->nod_arg[1]) PAR_error("Invalid array column reference"); } gpre_fld* field = 0; if (expr2) field = get_ref(expr2); if (!field) field = get_ref(expr1); if (!field) return; set_ref(expr1, field); if (!expr2) return; gpre_fld* temp = get_ref(expr1); if (temp) field = temp; set_ref(expr2, field); } //____________________________________________________________ // // The passed alias list fully specifies a relation. // The first alias represents a relation specified in // the from list at this scope levels. Subsequent // contexts, if there are any, represent base relations // in a view stack. They are used to fully specify a // base relation of a view. The aliases used in the // view stack are those used in the view definition. // static gpre_ctx* par_alias_list( gpre_req* request, gpre_nod* alias_list) { assert_IS_REQ(request); assert_IS_NOD(alias_list); gpre_nod** arg = alias_list->nod_arg; const gpre_nod* const* const end = alias_list->nod_arg + alias_list->nod_count; // check the first alias in the list with the relations // in the current context for a match gpre_rel* relation = 0; // unreliable test many lines below without initializing. gpre_ctx* context = par_alias(request, (const TEXT*) *arg); if (context) { if (alias_list->nod_count == 1) return context; relation = context->ctx_relation; } SCHAR error_string[ERROR_LENGTH]; // if the first alias didn't specify a table in the context stack, // look through all contexts to find one which might be a view with // a base table having a matching table name or alias if (!context) for (context = request->req_contexts; context; context = context->ctx_next) { if (context->ctx_scope_level != request->req_scope_level) continue; if (!context->ctx_relation) continue; if (relation = par_base_table(request, context->ctx_relation, (const TEXT*) *arg)) { break; } } if (!context) { fb_utils::snprintf(error_string, sizeof(error_string), "there is no alias or table named %s at this scope level", (TEXT*) *arg); PAR_error(error_string); } // find the base table using the specified alias list, skipping the first one // since we already matched it to the context for (arg++; arg < end; arg++) { if (!(relation = par_base_table(request, relation, (const TEXT*) *arg))) break; } if (!relation) { fb_utils::snprintf(error_string, sizeof(error_string), "there is no alias or table named %s at this scope level", (TEXT*) *arg); PAR_error(error_string); } // make up a dummy context to hold the resultant relation gpre_ctx* new_context = (gpre_ctx*) MSC_alloc(CTX_LEN); new_context->ctx_request = request; new_context->ctx_internal = context->ctx_internal; new_context->ctx_relation = relation; // concatenate all the contexts to form the alias name; // calculate the length leaving room for spaces and a null USHORT alias_length = alias_list->nod_count; for (arg = alias_list->nod_arg; arg < end; arg++) alias_length += strlen((TEXT*) *arg); TEXT* alias = (TEXT*) MSC_alloc(alias_length); // CVC: Warning: Using space as separator may conflict with dialect 3's embedded blanks; // but gpre is not much worried about dialects... except in this file! TEXT* p = new_context->ctx_alias = alias; for (arg = alias_list->nod_arg; arg < end; arg++) { for (const TEXT* q = (TEXT*) *arg; *q;) *p++ = *q++; *p++ = ' '; } p[-1] = 0; return new_context; } //____________________________________________________________ // // The passed relation or alias represents // a context which was previously specified // in the from list. Find and return the // proper context. // static gpre_ctx* par_alias( gpre_req* request, const TEXT* alias) { SCHAR error_string[ERROR_LENGTH]; assert_IS_REQ(request); // look through all contexts at this scope level // to find one that has a relation name or alias // name which matches the identifier passed gpre_ctx* relation_context = NULL; for (gpre_ctx* context = request->req_contexts; context; context = context->ctx_next) { if (context->ctx_scope_level != request->req_scope_level) continue; // check for matching alias if (context->ctx_alias) { const TEXT* p = context->ctx_alias; const TEXT* q = alias; for (; *p && *q; p++, q++) { if (UPPER(*p) != UPPER(*q)) break; } if (!*p && !*q) return context; } // check for matching relation name; aliases take priority so // save the context in case there is an alias of the same name; // also to check that there is no self-join in the query if (context->ctx_relation && !strcmp(context->ctx_relation->rel_symbol->sym_string, alias)) { if (relation_context) { fb_utils::snprintf(error_string, sizeof(error_string), "the table %s is referenced twice; use aliases to differentiate", alias); PAR_error(error_string); } relation_context = context; } } return relation_context; } //____________________________________________________________ // // Check if the relation in the passed context // has a base table which matches the passed alias. // static gpre_rel* par_base_table( gpre_req* request, const gpre_rel* relation, const TEXT* alias) { assert_IS_REQ(request); return MET_get_view_relation(request, relation->rel_symbol->sym_string, alias, 0); } //____________________________________________________________ // // Parse a CASE clause // // There are two types of CASE clauses. // 1) CASE WHEN (cond1) THEN value1 ... ELSE else_value END which is returned as nod_case // In this case nod_arg[0] = cond1, nod_arg[1] = value1, ... nod_arg[n] = else_value // 2) CASE value0 WHEN test_value1 THEN match_value1 ... ELSE else_value END which is // returned as nod_case1 // In this case nod_arg[0] = value0, nod_arg[1] = test_value1, nod_arg[2] = match_value1, ..., // nod_arg[n] = else_value // static gpre_nod* par_case(gpre_req* request) { gpre_lls* stack = NULL; int count = 0; nod_t nod_type; if (MSC_match(KW_WHEN)) { // Case 1 - CASE WHEN cond1 THEN value1 ... nod_type = nod_case; count++; MSC_push(SQE_boolean(request, NULL), &stack); while (MSC_match(KW_THEN)) { count++; MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack); if (MSC_match(KW_WHEN)) { count++; MSC_push(SQE_boolean(request, NULL), &stack); } } // If we have an odd number of nodes then we are missing a THEN if ((count % 2) == 1) { CPR_s_error("THEN"); } } else { // Case 2 - CASE value0 WHEN test_value1 THEN match_value1 ... nod_type = nod_case1; count++; MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack); while (MSC_match(KW_WHEN)) { count++; MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack); if (MSC_match(KW_THEN)) { count++; MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack); } else { CPR_s_error("THEN"); } } } // ELSE else_value END if (MSC_match(KW_ELSE)) { count++; MSC_push(SQE_value_or_null(request, false, NULL, NULL), &stack); } if (! MSC_match(KW_END)) { CPR_s_error("END"); } // Return a node containing all of the expressions parsed as part of CASE gpre_nod* node = MSC_node(nod_type, (SSHORT) count); gpre_nod** p = &node->nod_arg[count - 1]; while (stack) { *p-- = (gpre_nod*) MSC_pop(&stack); } return node; } //____________________________________________________________ // // Parse an AND boolean expression. // static gpre_nod* par_and( gpre_req* request, USHORT* paren_count) { assert_IS_REQ(request); gpre_nod* expr1 = par_not(request, paren_count); if (!MSC_match(KW_AND)) return expr1; return merge(expr1, par_and(request, paren_count)); } //____________________________________________________________ // // static gpre_nod* par_collate( gpre_req* request, gpre_nod* arg) { assert_IS_REQ(request); assert_IS_NOD(arg); gpre_nod* node = MSC_node(nod_cast, 2); node->nod_count = 1; node->nod_arg[0] = arg; gpre_fld* field = (gpre_fld*) MSC_alloc(FLD_LEN); node->nod_arg[1] = (gpre_nod*) field; CME_get_dtype(arg, field); if (field->fld_dtype > dtype_any_text) { // cast expression to VARYING with implementation-defined // maximum length field->fld_dtype = dtype_varying; field->fld_char_length = 30; field->fld_length = 0; // calculated by SQL_adjust_field_dtype field->fld_scale = 0; field->fld_sub_type = 0; } else if (field->fld_sub_type) { field->fld_character_set = MET_get_text_subtype(field->fld_sub_type); } SQL_par_field_collate(request, field); SQL_adjust_field_dtype(field); return node; } //____________________________________________________________ // // Parse a SQL "IN" expression. This comes in two flavors: // // IN () // IN (SELECT ) // static gpre_nod* par_in( gpre_req* request, gpre_nod* value) { gpre_nod* node; SCHAR s[ERROR_LENGTH]; assert_IS_REQ(request); assert_IS_NOD(value); EXP_left_paren(0); // If the next token isn't SELECT, we must have the comma list flavor. if (MSC_match(KW_SELECT)) node = implicit_any(request, value, nod_eq, nod_ansi_any); else { node = NULL; while (true) { gpre_nod* value2 = par_primitive_value(request, false, 0, NULL); if (value2->nod_type == nod_value) { ref* ref2 = (ref*) value2->nod_arg[0]; if (value->nod_type == nod_field) { ref* ref1 = (ref*) value->nod_arg[0]; ref2->ref_field = ref1->ref_field; } else { fb_utils::snprintf(s, sizeof(s), "datatype of %s can not be determined", gpreGlob.token_global.tok_string); PAR_error(s); } } if (!node) node = MSC_binary(nod_eq, value, value2); else node = MSC_binary(nod_or, node, MSC_binary(nod_eq, value, value2)); if (!MSC_match(KW_COMMA)) break; } } if (!EXP_match_paren()) return NULL; return node; } //____________________________________________________________ // // Parse a join relation clause. // static gpre_ctx* par_joined_relation( gpre_req* request) { gpre_ctx* context1; assert_IS_REQ(request); if (MSC_match(KW_LEFT_PAREN)) { context1 = par_joined_relation(request); EXP_match_paren(); } else if (!(context1 = SQE_context(request))) return NULL; return par_join_clause(request, context1); } //____________________________________________________________ // // Parse a join relation clause. // static gpre_ctx* par_join_clause( gpre_req* request, gpre_ctx* context1) { assert_IS_REQ(request); const nod_t join_type = par_join_type(); if (join_type == nod_nothing) return context1; gpre_ctx* context2 = par_joined_relation(request); if (!context2) CPR_s_error(""); if (!MSC_match(KW_ON)) CPR_s_error("ON"); gpre_nod* node = SQE_boolean(request, NULL); gpre_rse* selection = (gpre_rse*) MSC_alloc(RSE_LEN(2)); selection->rse_count = 2; selection->rse_context[0] = context1; selection->rse_context[1] = context2; selection->rse_boolean = node; selection->rse_join_type = join_type; context1 = MSC_context(request); context1->ctx_stream = selection; return par_join_clause(request, context1); } //____________________________________________________________ // // Parse a join type. // static nod_t par_join_type() { nod_t nod_type; if (MSC_match(KW_INNER)) nod_type = nod_join_inner; else if (MSC_match(KW_LEFT)) nod_type = nod_join_left; else if (MSC_match(KW_RIGHT)) nod_type = nod_join_right; else if (MSC_match(KW_FULL)) nod_type = nod_join_full; else if (MSC_match(KW_JOIN)) return nod_join_inner; else return nod_nothing; if (nod_type != nod_join_inner) MSC_match(KW_OUTER); if (!MSC_match(KW_JOIN)) CPR_s_error("JOIN"); return nod_type; } //____________________________________________________________ // // Parse either of the high precedence operators * and /. // static gpre_nod* par_multiply(gpre_req* request, bool aster_ok, USHORT* paren_count, bool* bool_flag) { assert_IS_REQ(request); gpre_nod* node = par_primitive_value(request, aster_ok, paren_count, bool_flag); if (node->nod_type == nod_asterisk) return node; if (gpreGlob.token_global.tok_keyword == KW_COLLATE) return par_collate(request, node); nod_t nod_type; while (true) { if (MSC_match(KW_ASTERISK)) nod_type = nod_times; else if (MSC_match(KW_SLASH)) nod_type = nod_divide; else return node; node = MSC_binary(nod_type, node, par_primitive_value(request, false, paren_count, bool_flag)); } } //____________________________________________________________ // // Parse an NOT boolean expression. // static gpre_nod* par_not( gpre_req* request, USHORT* paren_count) { assert_IS_REQ(request); if (MSC_match(KW_NOT)) return negate(par_not(request, paren_count)); nod_t type = nod_nothing; if (MSC_match(KW_EXISTS)) type = nod_any; else if (MSC_match(KW_SINGULAR)) type = nod_unique; if (type == nod_any || type == nod_unique) { scope saved_scope; push_scope(request, &saved_scope); EXP_left_paren(0); if (!MSC_match(KW_SELECT)) CPR_s_error("SELECT"); request->req_in_select_list++; gpre_nod* field; if (MSC_match(KW_ASTERISK)) field = NULL; else if (!(field = par_udf(request))) field = SQE_field(request, false); request->req_in_select_list--; gpre_nod* node = MSC_node(type, 1); node->nod_count = 0; gpre_rse* selection = par_rse(request, 0, false); node->nod_arg[0] = (gpre_nod*) selection; if (field) { SQE_resolve(&field, 0, selection); gpre_nod* expr = MSC_unary(nod_missing, field); selection->rse_boolean = merge(negate(expr), selection->rse_boolean); } EXP_rse_cleanup((gpre_rse*) node->nod_arg[0]); pop_scope(request, &saved_scope); EXP_match_paren(); return node; } return par_relational(request, paren_count); } //____________________________________________________________ // // Parse NULLIF built-in function. // // NULLIF(exp1, exp2) is really just a shortcut for // CASE exp1 WHEN exp2 THEN NULL ELSE exp1 END, so // we generate a nod_case1 node. static gpre_nod* par_nullif(gpre_req* request) { gpre_nod* node = MSC_node(nod_case1, 4); EXP_left_paren(0); node->nod_arg[0] = SQE_value(request, false, NULL, NULL); if (! MSC_match(KW_COMMA)) CPR_s_error("comma"); node->nod_arg[1] = SQE_value(request, false, NULL, NULL); node->nod_arg[2] = MSC_node(nod_null, 0); node->nod_arg[3] = node->nod_arg[0]; USHORT local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } //____________________________________________________________ // // Parse ORDER clause of SELECT expression. This is // particularly difficult since the ORDER clause can // refer to fields by position. // static void par_order(gpre_req* request, gpre_rse* select, bool union_f, bool view_flag) { gpre_nod* sort; USHORT i; assert_IS_REQ(request); if (!MSC_match(KW_ORDER)) return; if (view_flag) PAR_error("sort clause not allowed in a view definition"); MSC_match(KW_BY); gpre_lls* items = NULL; gpre_lls* directions = NULL; int count = 0; bool direction = false; gpre_nod* values = select->rse_fields; while (true) { direction = false; if (gpreGlob.token_global.tok_type == tok_number) { i = EXP_USHORT_ordinal(false); if (i < 1 || i > values->nod_count) CPR_s_error(""); sort = values->nod_arg[i - 1]; PAR_get_token(); if (gpreGlob.token_global.tok_keyword == KW_COLLATE) sort = par_collate(request, sort); } else { if (union_f) CPR_s_error(""); sort = SQE_value(request, false, NULL, NULL); map* request_map; if (request && (request_map = request->req_map)) sort = post_map(sort, request_map); } if (MSC_match(KW_ASCENDING)) direction = false; else if (MSC_match(KW_DESCENDING)) direction = true; count++; MSC_push((gpre_nod*)(IPTR) direction, &directions); MSC_push(sort, &items); if (!MSC_match(KW_COMMA)) break; } select->rse_sort = sort = MSC_node(nod_sort, (SSHORT) (count * 2)); sort->nod_count = count; gpre_nod** ptr = sort->nod_arg + count * 2; while (items) { *--ptr = (gpre_nod*) MSC_pop(&items); *--ptr = (gpre_nod*) MSC_pop(&directions); } } //____________________________________________________________ // // Allow the user to specify the access plan // for a query as part of a select expression. // static gpre_nod* par_plan( gpre_req* request) { assert_IS_REQ(request); // parse the join type nod_t nod_type; if (MSC_match(KW_JOIN)) nod_type = nod_join; else if (MSC_match(KW_MERGE)) nod_type = nod_merge; else if (MSC_match(KW_SORT) && MSC_match(KW_MERGE)) nod_type = nod_merge; else nod_type = nod_join; // make up the plan expression node gpre_nod* plan_expression = MSC_node(nod_plan_expr, 2); if (nod_type != nod_join) plan_expression->nod_arg[0] = MSC_node(nod_type, 0); // parse the plan items at this level EXP_left_paren(0); plan_expression->nod_arg[1] = SQE_list(par_plan_item, request, false); if (!EXP_match_paren()) return NULL; return plan_expression; } //____________________________________________________________ // // Parse an individual plan item for an // access plan. // static gpre_nod* par_plan_item(gpre_req* request, bool /*aster_ok*/, USHORT* /*paren_count*/, bool* /*bool_flag*/) { assert_IS_REQ(request); // check for a plan expression tok& token = gpreGlob.token_global; switch (token.tok_keyword) { case KW_JOIN: case KW_SORT: case KW_MERGE: case KW_LEFT_PAREN: return par_plan(request); } // parse the list of one or more table names or // aliases (more than one is used when there is // a need to differentiate base tables of a view) int count; gpre_lls* stack = NULL; for (count = 0; token.tok_type == tok_ident; count++) { if (token.tok_keyword == KW_NATURAL || token.tok_keyword == KW_ORDER || token.tok_keyword == KW_INDEX) { break; } MSC_push((gpre_nod*) upcase_string(token.tok_string), &stack); PAR_get_token(); } if (!count) CPR_s_error(" or "); gpre_nod** ptr; gpre_nod* alias_list = MSC_node(nod_list, (SSHORT) count); for (ptr = &alias_list->nod_arg[count]; stack;) *--ptr = (gpre_nod*) MSC_pop(&stack); // lookup the contexts for the aliases gpre_nod* index_list = NULL; gpre_ctx* context = par_alias_list(request, alias_list); // parse the access type gpre_nod* access_type = NULL; switch (token.tok_keyword) { case KW_NATURAL: access_type = MSC_node(nod_natural, 0); PAR_get_token(); break; case KW_ORDER: access_type = MSC_node(nod_index_order, 1); access_type->nod_count = 0; PAR_get_token(); if (token.tok_type != tok_ident) CPR_s_error(""); access_type->nod_arg[0] = (gpre_nod*) upcase_string(token.tok_string); PAR_get_token(); break; case KW_INDEX: access_type = MSC_node(nod_index, 1); access_type->nod_count = 0; PAR_get_token(); EXP_left_paren(0); stack = NULL; for (count = 0; token.tok_type == tok_ident;) { MSC_push((gpre_nod*) upcase_string(token.tok_string), &stack); PAR_get_token(); count++; if (!MSC_match(KW_COMMA)) break; } if (!count) CPR_s_error("
or "); index_list = MSC_node(nod_list, (SSHORT) count); access_type->nod_arg[0] = index_list; for (ptr = &index_list->nod_arg[count]; stack;) *--ptr = (gpre_nod*) MSC_pop(&stack); if (!EXP_match_paren()) return NULL; break; default: CPR_s_error("NATURAL, ORDER, or INDEX"); } // generate the plan item node gpre_nod* plan_item = MSC_node(nod_plan_item, 3); plan_item->nod_count = 2; plan_item->nod_arg[0] = alias_list; plan_item->nod_arg[1] = access_type; plan_item->nod_arg[2] = (gpre_nod*) context; return plan_item; } //____________________________________________________________ // // Parse a value expression. The value could be any of the // following: // // "quoted string" // _CHARSET"quoted string" // 123 // +1.234E-3 // field // relation.field // context.field // user defined function // static gpre_nod* par_primitive_value(gpre_req* request, bool aster_ok, USHORT* paren_count, bool* bool_flag) { USHORT local_count; assert_IS_REQ(request); gpre_nod* node = 0; if (!paren_count) { local_count = 0; paren_count = &local_count; } if (MSC_match(KW_SELECT)) return par_stat(request); if (MSC_match(KW_MINUS)) return MSC_unary(nod_negate, par_primitive_value(request, false, paren_count, false)); MSC_match(KW_PLUS); if (MSC_match(KW_USER)) { return MSC_node(nod_user_name, 0); } if (MSC_match(KW_VALUE)) { // If request is NULL we must be processing a subquery - and // without the request to refer to we're kinda hosed if (!request) PAR_error("VALUE cannot be used in this context"); const act* action = request->req_actions; if (request->req_type != REQ_ddl || !action || !(action->act_type == ACT_create_domain || action->act_type == ACT_alter_domain)) { PAR_error("VALUE cannot be used in this context"); } return MSC_node(nod_dom_value, 0); } if (MSC_match(KW_LEFT_PAREN)) { (*paren_count)++; if (bool_flag && *bool_flag) node = SQE_boolean(request, paren_count); else node = SQE_value(request, false, paren_count, bool_flag); EXP_match_paren(); (*paren_count)--; return node; } // Check for an aggregate statistical expression. If we already have a // map defined for the request, we're part of either HAVING or a trailing // ORDER clause. In this case, post only the complete expression, and not // the sub-expressions. map* tmp_map = NULL; for (const ops* op = stat_ops; op->rel_kw != KW_none; op++) { MSC_match(KW_ALL); if (MSC_match(op->rel_kw)) { if (request && (request->req_in_aggregate || !(request->req_in_select_list || request->req_in_having_clause || request->req_in_order_by_clause))) { // either nested aggregate, or not part of a select // list, having clause, or order by clause (in any subquery) PAR_error("Invalid aggregate reference"); } node = MSC_node(op->rel_op, 2); node->nod_count = 1; EXP_left_paren("left parenthesis in statistical function"); const bool distinct = MSC_match(KW_DISTINCT); if (request) { tmp_map = request->req_map; request->req_map = NULL; ++request->req_in_aggregate; } if (node->nod_type == nod_agg_count && MSC_match(KW_ASTERISK)) node->nod_count = 0; else { node->nod_arg[0] = SQE_value(request, false, NULL, NULL); // Disallow arrays as arguments to aggregate functions const gpre_nod* node_arg = node->nod_arg[0]; if (node_arg && node_arg->nod_type == nod_array) PAR_error("Array columns not permitted in aggregate functions"); } if (distinct) node->nod_arg[1] = node->nod_arg[0]; EXP_match_paren(); if (request) { if (tmp_map) node = post_map(node, tmp_map); request->req_map = tmp_map; --request->req_in_aggregate; } return node; } } // If it's a number or a quoted string, it's a literal if (gpreGlob.token_global.tok_type == tok_number || (isQuoted(gpreGlob.token_global.tok_type) && gpreGlob.sw_sql_dialect == 1) || gpreGlob.token_global.tok_type == tok_sglquoted) { node = EXP_literal(); return node; } // moved this timestamp support down some lines, because it caused // gpre to segfault when it was done here. // FSG 15.Nov.2000 // If the next token is a colon, it is a variable reference if (gpreGlob.token_global.tok_keyword == KW_COLON) { if (!request) { // We must be processing a subquery - and without the request to // post the :hostvar to we can't continue. // (core dump when de-refer of NULL request) PAR_error(":hostvar reference not supported in this context"); return NULL; } ref* reference = (ref*) SQE_variable(request, false, NULL, NULL); node = MSC_unary(nod_value, (gpre_nod*) reference); reference->ref_next = request->req_values; request->req_values = reference; return node; } // Must be a field or a udf. If there is a map, post the field to it. node = par_udf_or_field(request, aster_ok); //if (request && (map = request->req_map)) // return post_map (node, map); // I don't know what it's good for, but let's try it anyway if we haven't found // anything that makes sense until now if (!node) { // Begin date/time/timestamp support const kwwords_t kw_word = gpreGlob.token_global.tok_keyword; if (MSC_match(KW_DATE) || MSC_match(KW_TIME) || MSC_match(KW_TIMESTAMP)) { gpreGlob.token_global.tok_keyword = kw_word; node = EXP_literal(); return node; } // End date/time/timestamp support } return node; } //____________________________________________________________ // // Parse relational expression. // static gpre_nod* par_relational(gpre_req* request, USHORT* paren_count) { assert_IS_REQ(request); bool local_flag = true; gpre_nod* expr1 = SQE_value(request, false, paren_count, &local_flag); if (gpreGlob.token_global.tok_keyword == KW_RIGHT_PAREN) return expr1; if (gpreGlob.token_global.tok_keyword == KW_SEMI_COLON) { for (const nod_t* relational_ops = relationals; *relational_ops != nod_nothing; relational_ops++) { if (expr1->nod_type == *relational_ops) return expr1; } } bool negation = false; if (MSC_match(KW_NOT)) negation = true; // Check for one of the binary operators gpre_nod* node; if (MSC_match(KW_IN)) node = par_in(request, expr1); else if (MSC_match(KW_BETWEEN)) { node = MSC_node(nod_between, 3); node->nod_arg[0] = expr1; node->nod_arg[1] = SQE_value(request, false, NULL, NULL); MSC_match(KW_AND); node->nod_arg[2] = SQE_value(request, false, NULL, NULL); pair(node->nod_arg[0], node->nod_arg[1]); pair(node->nod_arg[0], node->nod_arg[2]); } else if (MSC_match(KW_LIKE)) { node = MSC_node(nod_like, 3); node->nod_arg[0] = expr1; node->nod_arg[1] = SQE_value(request, false, NULL, NULL); pair(node->nod_arg[0], node->nod_arg[1]); if (MSC_match(KW_ESCAPE)) { gpre_nod* expr2 = SQE_value(request, false, NULL, NULL); node->nod_arg[2] = expr2; if (expr2->nod_type == nod_value) { ref* ref_value = (ref*) expr2->nod_arg[0]; ref_value->ref_field = MET_make_field("like_escape_character", dtype_text, 2, false); } } else node->nod_count = 2; } else if (MSC_match(KW_IS)) { if (MSC_match(KW_NOT)) negation = !negation; if (MSC_match(KW_NULL)) { if (expr1->nod_type == nod_array) expr1->nod_type = nod_field; node = MSC_unary(nod_missing, expr1); } else if (MSC_match(KW_DISTINCT)) { if (!MSC_match(KW_FROM)) CPR_s_error("FROM"); node = MSC_binary(nod_equiv, expr1, SQE_value(request, false, NULL, NULL)); pair(node->nod_arg[0], node->nod_arg[1]); } else CPR_s_error("NULL or DISTINCT"); } else { node = NULL; const ops* op; for (op = rel_ops; op->rel_kw != KW_none; op++) { if (MSC_match(op->rel_kw)) break; } if (op->rel_kw == KW_none) { for (const nod_t* relational_ops = relationals; *relational_ops != nod_nothing; relational_ops++) { if (expr1->nod_type == *relational_ops) return expr1; } CPR_s_error(""); } if (op->rel_kw == KW_STARTING) MSC_match(KW_WITH); if (MSC_match(KW_ANY)) { if (!MSC_match(KW_LEFT_PAREN) || !MSC_match(KW_SELECT)) CPR_s_error(" for ALL"); if (op->rel_negation == nod_any || op->rel_negation == nod_ansi_any || op->rel_negation == nod_ansi_all) { CPR_s_error(" for ALL"); } node = implicit_any(request, expr1, op->rel_op, nod_ansi_all); EXP_match_paren(); } else { node = MSC_binary(op->rel_op, expr1, SQE_value(request, false, NULL, NULL)); pair(node->nod_arg[0], node->nod_arg[1]); } } return negation ? negate(node) : node; } // Out of alphabetical order. static bool resolve_fields(gpre_nod*& fields, gpre_rse* selection) { bool aggregate = false; gpre_nod** ptr = fields->nod_arg; int count = fields->nod_count; for (int i = 0; i < count; i++) { gpre_nod*& node = ptr[i]; if (node->nod_type == nod_asterisk) { const int old_count = count; fields = explode_asterisk(fields, i, selection); count = fields->nod_count; i += count - old_count; ptr = fields->nod_arg; } else { aggregate |= SQE_resolve(&node, NULL, selection); pair(node, 0); } } return aggregate; } //____________________________________________________________ // // Parse the SQL equivalent of a record selection expression -- // FROM, WHERE, and ORDER clauses. A field list may or may not // be present. // static gpre_rse* par_rse(gpre_req* request, gpre_nod* fields, bool distinct) { gpre_lls* stack = NULL; assert_IS_REQ(request); assert_IS_NOD(fields); // Get list and count of relations if (!MSC_match(KW_FROM)) CPR_s_error("FROM"); int count = 0; gpre_ctx* context; do { if (context = par_joined_relation(request)) { MSC_push((gpre_nod*) context, &stack); count++; } else return NULL; } while (MSC_match(KW_COMMA)); // Now allocate a record select expression // block for the beast and fill in what we already know. gpre_rse* select = (gpre_rse*) MSC_alloc(RSE_LEN(count)); select->rse_count = count; while (count--) select->rse_context[count] = (gpre_ctx*) MSC_pop(&stack); // If a field list has been presented, resolve references now bool aggregate = false; if (fields) aggregate = resolve_fields(fields, select); select->rse_fields = fields; if (distinct) select->rse_reduced = fields; // Handle a boolean, if present if (MSC_match(KW_WITH)) { ++request->req_in_where_clause; select->rse_boolean = SQE_boolean(request, 0); --request->req_in_where_clause; } if (MSC_match(KW_GROUP)) { MSC_match(KW_BY); select->rse_group_by = SQE_list(par_udf_or_field_with_collate, request, false); gpre_nod** ptr = select->rse_group_by->nod_arg; for (const gpre_nod* const* const end = ptr + select->rse_group_by->nod_count; ptr < end; ptr++) { if ((*ptr)->nod_type == nod_array) PAR_error("Array columns not permitted in GROUP BY clause"); } } if (select->rse_group_by || aggregate) { if (validate_references(select->rse_fields, select->rse_group_by)) PAR_error("simple column reference not allowed in aggregate context"); gpre_rse* sub_rse = select; map* subselect_map = (map*) MSC_alloc(sizeof(map)); sub_rse->rse_map = subselect_map; if (select->rse_group_by) request->req_map = subselect_map; subselect_map->map_context = MSC_context(request); select = (gpre_rse*) MSC_alloc(RSE_LEN(0)); select->rse_aggregate = sub_rse; if (fields) select->rse_fields = copy_fields(sub_rse->rse_fields, subselect_map); if (MSC_match(KW_HAVING)) { ++request->req_in_having_clause; select->rse_boolean = SQE_boolean(request, 0); --request->req_in_having_clause; if (validate_references(select->rse_boolean, sub_rse->rse_group_by)) { PAR_error("simple column reference in HAVING must be referenced in GROUP BY"); } } } // parse a user-specified access plan if (MSC_match(KW_PLAN)) select->rse_plan = par_plan(request); return select; } //____________________________________________________________ // // Parse a SELECT (sans keyword) expression (except UNION). This // is called exclusively by SQE_select, which handles unions. Note: // if "union_rse" is non-null, we are a subsequent SELECT in a union. // In this case, check datatypes of the field against the rse field // list. // static gpre_rse* par_select( gpre_req* request, gpre_rse* union_rse) { assert_IS_REQ(request); // Handle FIRST and SKIP clauses gpre_nod* rse_first = NULL; if (MSC_match(KW_FIRST)) { rse_first = MSC_node(nod_list, 1); rse_first->nod_arg[0] = SQE_value(request, false, NULL, NULL); } gpre_nod* rse_skip = NULL; if (MSC_match(KW_SKIP)) { rse_skip = MSC_node(nod_list, 1); rse_skip->nod_arg[0] = SQE_value(request, false, NULL, NULL); } // Handle the ALL and DISTINCT options const bool distinct = (!MSC_match(KW_ALL) && MSC_match(KW_DISTINCT)); // Make select list out of select items ++request->req_in_select_list; gpre_nod* s_list = SQE_list(SQE_value_or_null, request, true); --request->req_in_select_list; // If this is not a declare cursor statement and an INTO list is present, // parse it. gpre_nod* into_list = NULL; if (!(request->req_flags & REQ_sql_declare_cursor)) { into_list = MSC_match(KW_INTO) ? SQE_list(SQE_variable, request, false) : NULL; } gpre_rse* select = par_rse(request, s_list, distinct); if (rse_first) resolve_fields(rse_first, select); select->rse_sqlfirst = rse_first; if (rse_skip) resolve_fields(rse_skip, select); select->rse_sqlskip = rse_skip; if (select->rse_into = into_list) select->rse_flags |= RSE_singleton; if (union_rse && s_list->nod_count != union_rse->rse_fields->nod_count) PAR_error("select lists for UNION don't match"); return select; } //____________________________________________________________ // // Parse a dumb SQL scalar statistical expression. Somebody else // has already eaten the SELECT on the front. // static gpre_nod* par_stat( gpre_req* request) { assert_IS_REQ(request); request->req_in_subselect++; scope previous_scope; push_scope(request, &previous_scope); const bool distinct = (!MSC_match(KW_ALL) && MSC_match(KW_DISTINCT)); request->req_in_select_list++; gpre_nod* item = par_udf(request); if (!item) item = SQE_value(request, false, NULL, NULL); request->req_in_select_list--; gpre_nod* field_list = MSC_node(nod_list, 1); field_list->nod_arg[0] = item; gpre_rse* select = par_rse(request, field_list, distinct); select->rse_flags |= RSE_singleton; item = select->rse_fields->nod_arg[0]; gpre_nod* node = MSC_node(nod_via, 3); node->nod_count = 0; node->nod_arg[0] = (gpre_nod*) select; node->nod_arg[2] = MSC_node(nod_null, 0); node->nod_arg[1] = item; EXP_rse_cleanup(select); pop_scope(request, &previous_scope); request->req_in_subselect--; return node; } //____________________________________________________________ // // Parse a subscript value. // static gpre_nod* par_subscript( gpre_req* request) { assert_IS_REQ(request); ref* reference = (ref*) MSC_alloc(REF_LEN); gpre_nod* node = MSC_unary(nod_value, (gpre_nod*) reference); // Special case literals if (gpreGlob.token_global.tok_type == tok_number) { node->nod_type = nod_literal; char* string = (TEXT *) MSC_alloc(gpreGlob.token_global.tok_length + 1); reference->ref_value = string; MSC_copy(gpreGlob.token_global.tok_string, gpreGlob.token_global.tok_length, string); PAR_get_token(); return node; } if (!MSC_match(KW_COLON)) CPR_s_error(""); reference->ref_value = PAR_native_value(false, false); if (request) { reference->ref_next = request->req_values; request->req_values = reference; } return node; } //____________________________________________________________ // // Parse the SUBSTRING built-in function // static gpre_nod* par_substring(gpre_req* request) { gpre_nod* node = MSC_node(nod_substring, 3); EXP_left_paren(0); node->nod_arg[0] = SQE_value(request, false, NULL, NULL); if (! MSC_match(KW_FROM)) CPR_s_error("FROM"); node->nod_arg[1] = EXP_literal(); if (! node->nod_arg[1]) CPR_s_error("numeric literal"); const ref* reference = (ref*) node->nod_arg[1]->nod_arg[0]; const TEXT* string = reference->ref_value; if (*string == '"' || *string == '\'') CPR_s_error("numeric literal"); if (MSC_match(KW_FOR)) { node->nod_arg[2] = EXP_literal(); if (! node->nod_arg[2]) CPR_s_error("numeric literal"); reference = (ref*) node->nod_arg[2]->nod_arg[0]; string = reference->ref_value; if ((*string == '"') || (*string == '\'')) CPR_s_error("numeric literal"); } else { // No FOR clause given, fake up a numeric literal of 32767 (the max length // of a VarChar) to force copying to end of string. ref* reference1 = (ref*) MSC_alloc(REF_LEN); node->nod_arg[2] = MSC_unary(nod_literal, (gpre_nod*) reference1); string = (TEXT*) MSC_alloc(6); MSC_copy("32767", 5, (char*) string); reference1->ref_value = string; } USHORT local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } //____________________________________________________________ // // Match several trailing parentheses. // static void par_terminating_parens(USHORT* paren_count, USHORT* local_count) { // Suspicious condition. if (*paren_count && paren_count == local_count) do { EXP_match_paren(); } while (--(*paren_count)); } //____________________________________________________________ // // Parse a user defined function. If the current token isn't one, // return NULL. Otherwise try to parse one. If things go badly, // complain bitterly. // static gpre_nod* par_udf( gpre_req* request) { if (!request) return NULL; assert_IS_REQ(request); // Check for user defined functions // resolve only if an identifier if ((isQuoted(gpreGlob.token_global.tok_type)) || gpreGlob.token_global.tok_type == tok_ident) SQL_resolve_identifier("", NULL, NAME_SIZE); gpre_nod* node; USHORT local_count; udf* an_udf; if (request->req_database) an_udf = MET_get_udf(request->req_database, gpreGlob.token_global.tok_string); else { // no database was specified, check the metadata for all the databases // for the existence of the udf an_udf = NULL; for (gpre_dbb* db = gpreGlob.isc_databases; db; db = db->dbb_next) { udf* tmp_udf = MET_get_udf(db, gpreGlob.token_global.tok_string); if (tmp_udf) if (an_udf) { // udf was found in more than one database SCHAR s[ERROR_LENGTH]; sprintf(s, "UDF %s is ambiguous", gpreGlob.token_global.tok_string); PAR_error(s); } else { an_udf = tmp_udf; request->req_database = db; } } } if (an_udf) { if ((SQL_DIALECT_V5 == gpreGlob.sw_sql_dialect) && (dtype_sql_date == an_udf->udf_dtype || dtype_sql_time == an_udf->udf_dtype || dtype_int64 == an_udf->udf_dtype)) { SQL_dialect1_bad_type(an_udf->udf_dtype); } node = MSC_node(nod_udf, 2); node->nod_count = 1; node->nod_arg[1] = (gpre_nod*) an_udf; PAR_get_token(); EXP_left_paren(0); if (gpreGlob.token_global.tok_keyword != KW_RIGHT_PAREN) { // parse udf parameter references node->nod_arg[0] = SQE_list(SQE_value, request, false); if (an_udf->udf_args != node->nod_arg[0]->nod_count) PAR_error("count of UDF parameters doesn't match definition"); // Match parameter types to the declared parameters gpre_nod** input = node->nod_arg[0]->nod_arg; for (gpre_fld* field = an_udf->udf_inputs; field; input++, field = field->fld_next) { SQE_post_field(*input, field); } } else { node->nod_arg[0] = (gpre_nod*) 0; node->nod_count = 0; } local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } if (!request) return NULL; // Check for GEN_ID () if (MSC_match(KW_GEN_ID)) { TEXT* gen_name = (TEXT *) MSC_alloc(NAME_SIZE); node = MSC_node(nod_gen_id, 2); node->nod_count = 1; EXP_left_paren(0); SQL_resolve_identifier("", gen_name, NAME_SIZE); node->nod_arg[1] = (gpre_nod*) gen_name; PAR_get_token(); if (!MSC_match(KW_COMMA)) CPR_s_error(""); node->nod_arg[0] = SQE_value(request, false, NULL, NULL); local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } // Check for context variables // Begin date/time/timestamp if (MSC_match(KW_CURRENT_DATE)) return MSC_node(nod_current_date, 0); if (MSC_match(KW_CURRENT_TIME)) return MSC_node(nod_current_time, 0); if (MSC_match(KW_CURRENT_TIMESTAMP)) return MSC_node(nod_current_timestamp, 0); // End date/time/timestamp if (MSC_match(KW_CURRENT_CONNECTION)) return MSC_node(nod_current_connection, 0); if (MSC_match(KW_CURRENT_ROLE)) return MSC_node(nod_current_role, 0); if (MSC_match(KW_CURRENT_TRANSACTION)) return MSC_node(nod_current_transaction, 0); if (MSC_match(KW_CURRENT_USER)) return MSC_node(nod_user_name, 0); // End context variables // Check for SQL II defined functions // Begin date/time/timestamp if (MSC_match(KW_EXTRACT)) { node = MSC_node(nod_extract, 2); EXP_left_paren(0); const kwwords_t kw_word = gpreGlob.token_global.tok_keyword; if (MSC_match(KW_YEAR) || MSC_match(KW_MONTH) || MSC_match(KW_DAY) || MSC_match(KW_HOUR) || MSC_match(KW_MINUTE) || MSC_match(KW_SECOND) || MSC_match(KW_WEEKDAY) || MSC_match(KW_YEARDAY)) { node->nod_arg[0] = (gpre_nod*) kw_word; if (!MSC_match(KW_FROM)) CPR_s_error("FROM"); } else CPR_s_error("valid extract part"); node->nod_arg[1] = SQE_value(request, false, NULL, NULL); local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } // End date/time/timestamp if (MSC_match(KW_UPPER)) { node = MSC_node(nod_upcase, 1); EXP_left_paren(0); node->nod_arg[0] = SQE_value(request, false, NULL, NULL); local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } if (MSC_match(KW_LOWER)) { node = MSC_node(nod_lowcase, 1); EXP_left_paren(0); node->nod_arg[0] = SQE_value(request, false, NULL, NULL); local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } if (MSC_match(KW_CAST)) { node = MSC_node(nod_cast, 2); node->nod_count = 1; EXP_left_paren(0); node->nod_arg[0] = SQE_value_or_null(request, false, 0, 0); if (!MSC_match(KW_AS)) CPR_s_error("AS"); gpre_fld* field = (gpre_fld*) MSC_alloc(FLD_LEN); node->nod_arg[1] = (gpre_nod*) field; SQL_par_field_dtype(request, field, false); SQL_par_field_collate(request, field); SQL_adjust_field_dtype(field); local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } if (MSC_match(KW_COALESCE)) { node = MSC_node(nod_coalesce, 1); EXP_left_paren(0); node->nod_arg[0] = SQE_list(SQE_value, request, false); local_count = 1; par_terminating_parens(&local_count, &local_count); return node; } if (MSC_match(KW_CASE)) { node = par_case(request); return node; } if (MSC_match(KW_NULLIF)) { node = par_nullif(request); return node; } if (MSC_match(KW_SUBSTRING)) { node = par_substring(request); return node; } return NULL; } //____________________________________________________________ // // Parse a user defined function or a field name. // static gpre_nod* par_udf_or_field(gpre_req* request, bool aster_ok) { assert_IS_REQ(request); gpre_nod* node = par_udf(request); if (!node) node = SQE_field(request, aster_ok); return node; } //____________________________________________________________ // // Parse a user defined function or a field name. // Allow the collate clause to follow. // static gpre_nod* par_udf_or_field_with_collate(gpre_req* request, bool aster_ok, USHORT* /*paren_count*/, bool* /*bool_flag*/) { assert_IS_REQ(request); gpre_nod* node = par_udf_or_field(request, aster_ok); if (gpreGlob.token_global.tok_keyword == KW_COLLATE) node = par_collate(request, node); return node; } //____________________________________________________________ // // Parse FOR UPDATE WITH LOCK clause // static void par_update(gpre_rse* select, bool have_union, bool view_flag) { // Parse FOR UPDATE if present if (MSC_match(KW_FOR)) { if (! MSC_match(KW_UPDATE)) { CPR_s_error("UPDATE"); return; } if (MSC_match(KW_OF)) { do { CPR_token(); } while (MSC_match(KW_COMMA)); } select->rse_flags |= RSE_for_update; } // Parse WITH LOCK if present if (MSC_match(KW_WITH)) { if (! MSC_match(KW_LOCK)) { CPR_s_error("LOCK"); return; } if (have_union) { PAR_error("WITH LOCK in UNION"); return; } if (view_flag) { PAR_error("WITH LOCK in VIEW"); return; } select->rse_flags |= RSE_with_lock; } } //____________________________________________________________ // // Post a field or aggregate to a map. This is used to references // to aggregates and unions. Return a reference to the map (rather // than the expression itself). Post only the aggregates and fields, // not the computations around them. // static gpre_nod* post_fields( gpre_nod* node, map* to_map) { assert_IS_NOD(node); switch (node->nod_type) { // Removed during fix to BUG_8021 - this would post a literal to // the map record for each literal used in an expression - which // would result in unneccesary data movement as the literal is more // easily experssed in the assignment portion of the mapping select // operation. // case nod_literal: // 1995-Jul-10 David Schnepper case nod_field: case nod_agg_max: case nod_agg_min: case nod_agg_average: case nod_agg_total: case nod_agg_count: case nod_map_ref: return post_map(node, to_map); case nod_udf: case nod_gen_id: node->nod_arg[0] = post_fields(node->nod_arg[0], to_map); break; case nod_list: case nod_upcase: case nod_lowcase: case nod_concatenate: case nod_cast: case nod_plus: case nod_minus: case nod_times: case nod_divide: case nod_negate: { gpre_nod** ptr = node->nod_arg; for (const gpre_nod* const* const end = ptr + node->nod_count; ptr < end; ptr++) { *ptr = post_fields(*ptr, to_map); } break; } // Begin date/time/timestamp support case nod_extract: node->nod_arg[1] = post_fields(node->nod_arg[1], to_map); break; // End date/time/timestamp support } return node; } //____________________________________________________________ // // Post a value expression to a map. This is used to references // to aggregates and unions. Return a reference to the map (rather // than the expression itself). // static gpre_nod* post_map( gpre_nod* node, map* to_map) { mel* element; assert_IS_NOD(node); // Search existing map for equivalent expression. If we find one, // return a reference to it. if (node->nod_type == nod_map_ref) { element = (mel*) node->nod_arg[0]; if (element->mel_context == to_map->map_context) return node; } for (element = to_map->map_elements; element; element = element->mel_next) { if (compare_expr(node, element->mel_expr)) return MSC_unary(nod_map_ref, (gpre_nod*) element); } // We need to make up a new map reference element = (mel*) MSC_alloc(sizeof(mel)); element->mel_next = to_map->map_elements; to_map->map_elements = element; element->mel_position = to_map->map_count++; element->mel_expr = node; element->mel_context = to_map->map_context; // Make up a reference to the map element return MSC_unary(nod_map_ref, (gpre_nod*) element); } //____________________________________________________________ // // Copy a selection list to the map generated for a UNION // construct. Note at this level we want the full expression // selected posted, not just the portions that come from the // stream. Thus CAST and other operations will be passed into // a UNION. See BUG_8021 & BUG_8000 for examples. // static gpre_nod* post_select_list( gpre_nod* fields, map* to_map) { assert_IS_NOD(fields); gpre_nod* list = MSC_node(nod_list, fields->nod_count); for (USHORT i = 0; i < fields->nod_count; i++) list->nod_arg[i] = post_map(fields->nod_arg[i], to_map); return list; } //____________________________________________________________ // // Restore saved scoping information to the request block // static void pop_scope(gpre_req* request, scope* save_scope) { assert_IS_REQ(request); request->req_contexts = save_scope->req_contexts; request->req_scope_level = save_scope->req_scope_level; request->req_in_aggregate = save_scope->req_in_aggregate; request->req_in_select_list = save_scope->req_in_select_list; request->req_in_where_clause = save_scope->req_in_where_clause; request->req_in_having_clause = save_scope->req_in_having_clause; request->req_in_order_by_clause = save_scope->req_in_order_by_clause; } //____________________________________________________________ // // Save scoping information from the request block // static void push_scope(gpre_req* request, scope* save_scope) { assert_IS_REQ(request); save_scope->req_contexts = request->req_contexts; save_scope->req_scope_level = request->req_scope_level; save_scope->req_in_aggregate = request->req_in_aggregate; save_scope->req_in_select_list = request->req_in_select_list; save_scope->req_in_where_clause = request->req_in_where_clause; save_scope->req_in_having_clause = request->req_in_having_clause; save_scope->req_in_order_by_clause = request->req_in_order_by_clause; save_scope->req_in_subselect = request->req_in_subselect; request->req_scope_level++; request->req_in_aggregate = 0; request->req_in_select_list = 0; request->req_in_where_clause = 0; request->req_in_having_clause = 0; request->req_in_order_by_clause = 0; request->req_in_subselect = 0; } //____________________________________________________________ // // Attempt to resolve a field in a context. If successful, return // the field. Otherwise return NULL. Let somebody else worry about // errors. // static gpre_fld* resolve(gpre_nod* node, gpre_ctx* context, gpre_ctx** found_context, act** slice_action) { gpre_fld* field; assert_IS_NOD(node); gpre_rse* rs_stream = context->ctx_stream; if (rs_stream) { for (SSHORT i = 0; i < rs_stream->rse_count; i++) if (field = resolve(node, rs_stream->rse_context[i], found_context, slice_action)) { return field; } return NULL; } tok* f_token = (tok*) node->nod_arg[0]; tok* q_token = (tok*) node->nod_arg[1]; if (!(context->ctx_relation || context->ctx_procedure)) return NULL; // Handle unqualified fields first for simplicity if (!q_token) field = MET_context_field(context, f_token->tok_string); else { // Now search alternatives for the qualifier gpre_sym* symbol = HSH_lookup(q_token->tok_string); // This caused gpre to dump core if there are lower case // table aliases in a where clause used with dialect 2 or 3 //if ( (symbol == NULL) && (sw_case || gpreGlob.sw_sql_dialect == SQL_DIALECT_V5)) // symbol = HSH_lookup2 (q_token->tok_string); // So I replaced it with the following, don't know // why we don't do a HSH_lookup2 in any case, but so it may be. // FSG 16.Nov.2000 if (symbol == NULL) symbol = HSH_lookup2(q_token->tok_string); for (gpre_sym* temp_symbol = symbol; temp_symbol; temp_symbol = temp_symbol->sym_homonym) { if (temp_symbol->sym_type == SYM_context) { symbol = temp_symbol; break; } if (temp_symbol->sym_type == SYM_relation) { symbol = temp_symbol; continue; } if (temp_symbol->sym_type == SYM_procedure) { if (symbol->sym_type == SYM_relation) continue; symbol = temp_symbol; } } field = NULL; switch (symbol->sym_type) { case SYM_relation: if ((gpre_rel*) symbol->sym_object == context->ctx_relation) field = MET_field(context->ctx_relation, f_token->tok_string); break; case SYM_procedure: if ((gpre_prc*) symbol->sym_object == context->ctx_procedure) field = MET_context_field(context, f_token->tok_string); break; case SYM_context: if (symbol->sym_object == context) field = MET_context_field(context, f_token->tok_string); break; } } if (field && found_context) *found_context = context; // Check for valid array field // Check dimensions // Set remaining fields for slice slc* slice; gpre_req* slice_req = (gpre_req*) node->nod_arg[2]; if (slice_req && (slice = slice_req->req_slice) && slice_action) { slice = slice_req->req_slice; ary* ary_info = field->fld_array_info; if (!ary_info) CPR_s_error(""); if (ary_info->ary_dimension_count != slice->slc_dimensions) PAR_error("subscript count mismatch"); slice->slc_field = field; slice->slc_parent_request = context->ctx_request; // The action type maybe ACT_get_slice or ACT_put_slice // set as a place holder act* action = MSC_action(slice_req, ACT_get_slice); action->act_object = (ref*) slice; *slice_action = action; } else if ((slice_req = (gpre_req*) node->nod_arg[2]) && slice_action) { // The action type maybe ACT_get_slice or ACT_put_slice // set as a place holder act* action = MSC_action(slice_req, ACT_get_slice); *slice_action = action; } return field; } //____________________________________________________________ // // Attempt to resolve an asterisk in a context. // If successful, return the context. Otherwise return NULL. // static gpre_ctx* resolve_asterisk( const tok* q_token, gpre_rse* selection) { for (int i = 0; i < selection->rse_count; i++) { gpre_ctx* context = selection->rse_context[i]; gpre_rse* rs_stream = context->ctx_stream; if (rs_stream) { if (context = resolve_asterisk(q_token, rs_stream)) return context; continue; } gpre_sym* symbol = HSH_lookup(q_token->tok_string); for (; symbol; symbol = symbol->sym_homonym) { if (symbol->sym_type == SYM_relation && (gpre_rel*) symbol->sym_object == context->ctx_relation) { return context; } if (symbol->sym_type == SYM_procedure && (gpre_prc*) symbol->sym_object == context->ctx_procedure) { return context; } if (symbol->sym_type == SYM_context && (gpre_ctx*) symbol->sym_object == context) { return context; } } } return NULL; } //____________________________________________________________ // // Set field reference for any host variables in expr to field_ref. // static void set_ref( gpre_nod* expr, gpre_fld* field_ref) { assert_IS_NOD(expr); ref* re = (ref*) expr->nod_arg[0]; switch (expr->nod_type) { case nod_value: re->ref_field = field_ref; break; case nod_agg_count: case nod_agg_max: case nod_agg_min: case nod_agg_total: case nod_agg_average: case nod_plus: case nod_minus: case nod_times: case nod_divide: case nod_negate: case nod_upcase: case nod_lowcase: case nod_concatenate: case nod_cast: { gpre_nod** ptr = expr->nod_arg; for (const gpre_nod* const* const end = ptr + expr->nod_count; ptr < end; ptr++) { set_ref(*ptr, field_ref); } break; } // Begin date/time/timestamp support case nod_extract: set_ref(expr->nod_arg[1], field_ref); break; // End date/time/timestamp support } } //____________________________________________________________ // // Return the uppercase version of // the input string. // static char* upcase_string(const char* p) { char* const s = (char *) MSC_alloc(strlen(p) + 1); char* q = s; USHORT l = 0; char c; while ((c = *p++) && (++l <= NAME_SIZE)) { *q++ = UPPER7(c); } *q = 0; return s; } //____________________________________________________________ // // validate that top level field references // in a select with a group by, real or imagined, // resolve to grouping fields. Ignore constants // and aggregates. If there's no group_by list, // then it's an imaginary group by (a top level // aggregation, and nothing can be referenced // directly. // static bool validate_references(const gpre_nod* fields, const gpre_nod* group_by) { assert_IS_NOD(fields); assert_IS_NOD(group_by); if (!fields) return false; if (fields->nod_type == nod_field) { if (!group_by) return true; const ref* fref = (ref*) fields->nod_arg[0]; bool context_match = false; const gpre_nod* const* ptr = group_by->nod_arg; for (const gpre_nod* const* const end = ptr + group_by->nod_count; ptr < end; ptr++) { const ref* gref = (ref*) (*ptr)->nod_arg[0]; if (gref->ref_context == fref->ref_context) { if (gref->ref_field == fref->ref_field) return false; context_match = true; } } return context_match; } switch (fields->nod_type) { case nod_agg_count: case nod_agg_max: case nod_agg_min: case nod_agg_total: case nod_agg_average: case nod_aggregate: return false; } if (fields->nod_type == nod_any || fields->nod_type == nod_ansi_any || fields->nod_type == nod_ansi_all) { const gpre_rse* any = (gpre_rse*) fields->nod_arg[0]; return validate_references(any->rse_boolean, group_by); } if (fields->nod_type == nod_gen_id || fields->nod_type == nod_udf) return validate_references(fields->nod_arg[0], group_by); bool invalid = false; const gpre_nod* const* ptr = fields->nod_arg; for (const gpre_nod* const* const end = ptr + fields->nod_count; ptr < end; ptr++) { switch ((*ptr)->nod_type) { case nod_map_ref: { const mel* element = (mel*) (*ptr)->nod_arg[0]; const gpre_nod* node = element->mel_expr; if (node->nod_type != nod_agg_count && node->nod_type != nod_agg_max && node->nod_type != nod_agg_min && node->nod_type != nod_agg_total && node->nod_type != nod_agg_average && node->nod_type != nod_aggregate) { invalid |= validate_references(node, group_by); } break; } case nod_field: case nod_plus: case nod_minus: case nod_times: case nod_negate: case nod_divide: case nod_and: case nod_like: case nod_missing: case nod_not: case nod_or: case nod_eq: case nod_ne: case nod_gt: case nod_ge: case nod_le: case nod_lt: case nod_upcase: case nod_lowcase: case nod_concatenate: case nod_cast: invalid |= validate_references(*ptr, group_by); break; } } return invalid; }