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