/* * PROGRAM: JRD Access Method * MODULE: cmp.c * DESCRIPTION: Request compiler * * 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): ______________________________________. * * 2001.07.28: John Bellardo: Added code to handle rse_skip. * 2001.07.17 Claudio Valderrama: Stop crash when parsing user-supplied SQL plan. * 2001.10.04 Claudio Valderrama: Fix annoying & invalid server complaint about * triggers not having REFERENCES privilege over their owner table. * 2002.02.24 Claudio Valderrama: substring() should signal output as string even * if source is blob and should check implementation limits on field lengths. * 2002.02.25 Claudio Valderrama: concatenate() should be a civilized function. * This closes the heart of SF Bug #518282. */ /* $Id: cmp.cpp,v 1.10 2002-09-19 16:02:56 skidder Exp $ */ #include "firebird.h" #include "../jrd/ibsetjmp.h" #include #include /* abort */ #include "../jrd/common.h" #include "../jrd/gds.h" #include "../jrd/jrd.h" #include "../jrd/req.h" #include "../jrd/val.h" #include "../jrd/align.h" #include "../jrd/lls.h" #include "../jrd/exe.h" #include "../jrd/rse.h" #include "../jrd/scl.h" #include "../jrd/tra.h" #include "../jrd/all.h" #include "../jrd/lck.h" #include "../jrd/irq.h" #include "../jrd/drq.h" #include "../jrd/license.h" #include "../jrd/intl.h" #include "../jrd/rng.h" #include "../jrd/btr.h" #include "../jrd/constants.h" #include "../jrd/gdsassert.h" #include "../jrd/all_proto.h" #include "../jrd/cmp_proto.h" #include "../jrd/dsc_proto.h" #include "../jrd/err_proto.h" #include "../jrd/exe_proto.h" #include "../jrd/fun_proto.h" #include "../jrd/gds_proto.h" #include "../jrd/idx_proto.h" #include "../jrd/lck_proto.h" #include "../jrd/opt_proto.h" #include "../jrd/par_proto.h" #include "../jrd/rng_proto.h" #include "../jrd/sbm_proto.h" #include "../jrd/scl_proto.h" #include "../jrd/thd_proto.h" #include "../jrd/met_proto.h" #include "../jrd/mov_proto.h" #include "../jrd/dsc_proto.h" #include "../jrd/dbg_proto.h" /* DBG_supervisor */ /* Pick up relation ids */ #define RELATION(name,id,ods) id, #define FIELD(symbol,name,id,update,ods,new_id,new_ods) #define END_RELATION typedef ENUM rids { #include "../jrd/relations.h" rel_MAX} RIDS; #undef RELATION #undef FIELD #undef END_RELATION /* InterBase provides transparent conversion from string to date in * contexts where it makes sense. This macro checks a descriptor to * see if it is something that *could* represent a date value */ #define COULD_BE_DATE(d) ((DTYPE_IS_DATE((d).dsc_dtype)) || ((d).dsc_dtype <= dtype_any_text)) /* One of d1,d2 is time, the other is date */ #define IS_DATE_AND_TIME(d1,d2) \ ((((d1).dsc_dtype==dtype_sql_time)&&((d2).dsc_dtype==dtype_sql_date)) || \ (((d2).dsc_dtype==dtype_sql_time)&&((d1).dsc_dtype==dtype_sql_date))) #define REQ_TAIL sizeof (((REQ) 0)->req_rpb[0]) #define MAP_LENGTH 256 /* RITTER - changed HP10 to HPUX */ #if defined (HPUX) && defined (SUPERSERVER) #define MAX_RECURSION 96 #endif #ifndef MAX_RECURSION #define MAX_RECURSION 128 #endif #if (defined PC_PLATFORM && !defined NETWARE_386) #define MAX_REQUEST_SIZE 65534 #else #define MAX_REQUEST_SIZE 262144 #endif #ifdef SHLIB_DEFS #undef access #endif static UCHAR *alloc_map(TDBB, CSB *, USHORT); static NOD catenate_nodes(TDBB, LLS); static NOD copy(TDBB, CSB *, NOD, UCHAR *, USHORT, USHORT); static void expand_view_nodes(TDBB, CSB, USHORT, LLS *, NOD_T); static void ignore_dbkey(TDBB, CSB, RSE, REL); static NOD make_defaults(TDBB, CSB *, USHORT, NOD); static NOD make_validation(TDBB, CSB *, USHORT); static NOD pass1(TDBB, CSB *, NOD, REL, USHORT, BOOLEAN); static void pass1_erase(TDBB, CSB *, NOD); static NOD pass1_expand_view(TDBB, CSB, USHORT, USHORT, USHORT); static void pass1_modify(TDBB, CSB *, NOD); static RSE pass1_rse(TDBB, CSB *, RSE, REL, USHORT); static void pass1_source(TDBB, CSB *, RSE, NOD, NOD *, LLS *, REL, USHORT); static NOD pass1_store(TDBB, CSB *, NOD); static NOD pass1_update(TDBB, CSB *, REL, TRIG_VEC, USHORT, USHORT, USHORT, REL, USHORT); static NOD pass2(TDBB, register CSB, register NOD, NOD); static void pass2_rse(TDBB, CSB, RSE); static NOD pass2_union(TDBB, CSB, NOD); static void plan_check(CSB, RSE); static void plan_set(CSB, RSE, NOD); static void post_procedure_access(TDBB, CSB, PRC); static RSB post_rse(TDBB, CSB, RSE); static void post_trigger_access(TDBB, CSB, REL, TRIG_VEC, REL); static void process_map(TDBB, CSB, NOD, FMT *); static BOOLEAN stream_in_rse(USHORT, RSE); static SSHORT strcmp_space(TEXT *, TEXT *); #ifdef PC_ENGINE static USHORT base_stream(CSB, NOD *, BOOLEAN); #endif int DLL_EXPORT CMP_clone_active(REQ request) { /************************************** * * C M P _ c l o n e _ a c t i v e * ************************************** * * Functional description * Determine if a request or any of its clones are active. * **************************************/ VEC vector; vec::iterator sub_req, end; DEV_BLKCHK(request, type_req); if (request->req_flags & req_in_use) return TRUE; if ( (vector = request->req_sub_requests) ) for (sub_req = vector->begin(), end = vector->end(); sub_req < end; sub_req++) if (*sub_req && ((REQ)(*sub_req))->req_flags & req_in_use) return TRUE; return FALSE; } NOD DLL_EXPORT CMP_clone_node(TDBB tdbb, CSB csb, NOD node) { /************************************** * * C M P _ c l o n e _ n o d e * ************************************** * * Functional description * Clone a value node for the optimizer. Make a copy of the node * (if necessary) and assign impure space. * **************************************/ NOD clone; SET_TDBB(tdbb); DEV_BLKCHK(csb, type_csb); DEV_BLKCHK(node, type_nod); if (node->nod_type == nod_argument) return node; clone = copy(tdbb, &csb, node, NULL, 0, FALSE); pass2(tdbb, csb, clone, 0); return clone; } REQ DLL_EXPORT CMP_clone_request(TDBB tdbb, REQ request, USHORT level, BOOLEAN validate) { /************************************** * * C M P _ c l o n e _ r e q u e s t * ************************************** * * Functional description * Get the incarnation of the request appropriate for a given level. * If the incarnation doesn't exist, clone the request. * **************************************/ REQ clone; VEC vector; RPB *rpb1, *rpb2, *end; USHORT n; ACC access; SCL class_; PRC procedure; TEXT *prc_sec_name; DEV_BLKCHK(request, type_req); SET_TDBB(tdbb); /* Find the request if we've got it. */ if (!level) return request; if ((vector = request->req_sub_requests) && level < vector->count() && (clone = (REQ) (*vector)[level])) return clone; /* We need to clone the request -- find someplace to put it */ if (validate) { if ( (procedure = request->req_procedure) ) { prc_sec_name = (procedure->prc_security_name ? (TEXT *) procedure-> prc_security_name->str_data : (TEXT *) 0); class_ = SCL_get_class(prc_sec_name); SCL_check_access(class_, 0, 0, 0, SCL_execute, object_procedure, reinterpret_cast < char *>(procedure->prc_name->str_data)); } for (access = request->req_access; access; access = access->acc_next) { class_ = SCL_get_class(access->acc_security_name); SCL_check_access(class_, access->acc_view, access->acc_trg_name, access->acc_prc_name, access->acc_mask, access->acc_type, access->acc_name); } } if (!vector) { vector = request->req_sub_requests = vec::newVector(*request->req_pool, level+1); } if (level >= vector->count()) vector->resize(level + 1); /* Clone the request */ n = (USHORT) ((request->req_impure_size - REQ_SIZE + REQ_TAIL - 1) / REQ_TAIL); clone = new(*request->req_pool, n) req; (*vector)[level] = (BLK) clone; clone->req_attachment = tdbb->tdbb_attachment; clone->req_count = request->req_count; clone->req_pool = request->req_pool; clone->req_impure_size = request->req_impure_size; clone->req_top_node = request->req_top_node; clone->req_trg_name = request->req_trg_name; clone->req_flags = request->req_flags & REQ_FLAGS_CLONE_MASK; rpb1 = clone->req_rpb; end = rpb1 + clone->req_count; for (rpb2 = request->req_rpb; rpb1 < end; rpb1++, rpb2++) { if (rpb2->rpb_stream_flags & RPB_s_update) rpb1->rpb_stream_flags |= RPB_s_update; rpb1->rpb_relation = rpb2->rpb_relation; } return clone; } REQ DLL_EXPORT CMP_compile(USHORT blr_length, UCHAR * blr, USHORT internal_flag) { /************************************** * * C M P _ c o m p i l e * ************************************** * * Functional description * Compile a BLR request. * Wrapper for CMP_compile2 - an API change * was made for CMP_compile, but as calls to this * are generated by gpre it's necessary to have a * wrapper function to keep the build from breaking. * This function can be removed after the next full * product build is completed. * 1997-Jan-20 David Schnepper * **************************************/ return CMP_compile2(GET_THREAD_DATA, blr, internal_flag); } REQ DLL_EXPORT CMP_compile2(TDBB tdbb, UCHAR* blr, USHORT internal_flag) { /************************************** * * C M P _ c o m p i l e 2 * ************************************** * * Functional description * Compile a BLR request. * **************************************/ REQ request = 0; ACC access; SET_TDBB(tdbb); JrdMemoryPool* old_pool = tdbb->tdbb_default; JrdMemoryPool* new_pool = new(*tdbb->tdbb_database->dbb_permanent) JrdMemoryPool; tdbb->tdbb_default = new_pool; try { CSB csb = PAR_parse(tdbb, blr, internal_flag); request = CMP_make_request(tdbb, &csb); if (internal_flag) { request->req_flags |= req_internal; } for (access = request->req_access; access; access = access->acc_next) { SCL class_ = SCL_get_class(access->acc_security_name); SCL_check_access(class_, access->acc_view, access->acc_trg_name, access->acc_prc_name, access->acc_mask, access->acc_type, access->acc_name); } delete csb; tdbb->tdbb_default = old_pool; } catch (...) { tdbb->tdbb_default = old_pool; if (request) { CMP_release(tdbb, request); } else if (new_pool) { // TMN: Are we not to release the pool, just beqause // we have a request?! delete new_pool; } ERR_punt(); } return request; } csb_repeat* DLL_EXPORT CMP_csb_element(CSB* csb, USHORT element) { /************************************** * * C M P _ c s b _ e l e m e n t * ************************************** * * Functional description * Find tail element of compile scratch block. If the csb isn't big * enough, extend it. * **************************************/ DEV_BLKCHK(*csb, type_csb); if (element >= (*csb)->csb_rpt.size()) { (*csb)->csb_rpt.resize(element + 5); (*csb)->csb_count = element + 5; } return &(*csb)->csb_rpt[element]; } void DLL_EXPORT CMP_expunge_transaction(TRA transaction) { /************************************** * * C M P _ e x p u n g e _ t r a n s a c t i o n * ************************************** * * Functional description * Get rid of all references to a given transaction in existing * requests. * **************************************/ VEC vector; REQ request; vec::iterator sub, end; DEV_BLKCHK(transaction, type_tra); for (request = transaction->tra_attachment->att_requests; request; request = request->req_request) { if (request->req_transaction == transaction) request->req_transaction = NULL; if ( (vector = request->req_sub_requests) ) for (sub = vector->begin(), end = vector->end(); sub < end; sub++) if (*sub && ((REQ)(*sub))->req_transaction == transaction) ((REQ)(*sub))->req_transaction = NULL; } } REQ DLL_EXPORT CMP_find_request(TDBB tdbb, USHORT id, USHORT which) { /************************************** * * C M P _ f i n d _ r e q u e s t * ************************************** * * Functional description * Find an inactive incarnation of a system request. If necessary, * clone it. * **************************************/ DBB dbb; REQ request, clone; USHORT n; SET_TDBB(tdbb); dbb = tdbb->tdbb_database; CHECK_DBB(dbb); /* If the request hasn't been compiled or isn't active, there're nothing to do */ THD_MUTEX_LOCK(dbb->dbb_mutexes + DBB_MUTX_cmp_clone); if ((which == IRQ_REQUESTS && !(request = (REQ) REQUEST(id))) || (which == DYN_REQUESTS && !(request = (REQ) DYN_REQUEST(id))) || !(request->req_flags & (req_active | req_reserved))) { if (request) request->req_flags |= req_reserved; THD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_cmp_clone); return request; } /* Request exists and is in use. Look for clones until we find one that is available */ for (n = 1;; n++) { if (n > MAX_RECURSION) { THD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_cmp_clone); ERR_post(gds_no_meta_update, gds_arg_gds, gds_req_depth_exceeded, gds_arg_number, (SLONG) MAX_RECURSION, 0); /* Msg363 "request depth exceeded. (Recursive definition?)" */ } clone = CMP_clone_request(tdbb, request, n, FALSE); if (!(clone->req_flags & (req_active | req_reserved))) { clone->req_flags |= req_reserved; THD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_cmp_clone); return clone; } } } void DLL_EXPORT CMP_fini(TDBB tdbb) { /************************************** * * C M P _ f i n i * ************************************** * * Functional description * Get rid of resource locks during shutdown. * **************************************/ SET_TDBB(tdbb); CMP_shutdown_database(tdbb); } FMT DLL_EXPORT CMP_format(TDBB tdbb, CSB csb, USHORT stream) { /************************************** * * C M P _ f o r m a t * ************************************** * * Functional description * Pick up a format for a stream. * **************************************/ csb_repeat *tail; SET_TDBB(tdbb); DEV_BLKCHK(csb, type_csb); tail = &csb->csb_rpt[stream]; if (tail->csb_format) return tail->csb_format; if (tail->csb_relation) return tail->csb_format = MET_current(tdbb, tail->csb_relation); else if (tail->csb_procedure) return tail->csb_format = tail->csb_procedure->prc_format; IBERROR(222); /* msg 222 bad blr -- invalid stream */ return ((FMT) NULL); } void DLL_EXPORT CMP_get_desc( TDBB tdbb, register CSB csb, register NOD node, DSC * desc) { /************************************** * * C M P _ g e t _ d e s c * ************************************** * * Functional description * Compute descriptor for value expression. * **************************************/ USHORT dtype, dtype1, dtype2; SET_TDBB(tdbb); DEV_BLKCHK(csb, type_csb); DEV_BLKCHK(node, type_nod); switch (node->nod_type) { case nod_max: case nod_min: case nod_from: CMP_get_desc(tdbb, csb, node->nod_arg[e_stat_value], desc); return; case nod_agg_total: case nod_agg_total_distinct: case nod_total: if (node->nod_type == nod_total) CMP_get_desc(tdbb, csb, node->nod_arg[e_stat_value], desc); else CMP_get_desc(tdbb, csb, node->nod_arg[0], desc); switch (dtype = desc->dsc_dtype) { case dtype_short: desc->dsc_dtype = dtype_long; desc->dsc_length = sizeof(SLONG); node->nod_scale = desc->dsc_scale; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; case dtype_null: desc->dsc_dtype = dtype_null; desc->dsc_length = 0; node->nod_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; case dtype_long: case dtype_int64: case dtype_real: case dtype_double: #ifdef VMS case dtype_d_float: #endif case dtype_text: case dtype_cstring: case dtype_varying: desc->dsc_dtype = DEFAULT_DOUBLE; desc->dsc_length = sizeof(double); desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; node->nod_flags |= nod_double; return; case dtype_quad: desc->dsc_dtype = dtype_quad; desc->dsc_length = sizeof(SQUAD); desc->dsc_sub_type = 0; desc->dsc_flags = 0; node->nod_scale = desc->dsc_scale; node->nod_flags |= nod_quad; #ifdef NATIVE_QUAD return; #endif default: assert(FALSE); /* FALLINTO */ case dtype_sql_time: case dtype_sql_date: case dtype_timestamp: case dtype_blob: case dtype_array: /* break to error reporting code */ break; } break; case nod_agg_total2: case nod_agg_total_distinct2: CMP_get_desc(tdbb, csb, node->nod_arg[0], desc); switch (dtype = desc->dsc_dtype) { case dtype_short: case dtype_long: case dtype_int64: desc->dsc_dtype = dtype_int64; desc->dsc_length = sizeof(SINT64); node->nod_scale = desc->dsc_scale; desc->dsc_flags = 0; return; case dtype_null: desc->dsc_dtype = dtype_null; desc->dsc_length = 0; node->nod_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; case dtype_real: case dtype_double: #ifdef VMS case dtype_d_float: #endif case dtype_text: case dtype_cstring: case dtype_varying: desc->dsc_dtype = DEFAULT_DOUBLE; desc->dsc_length = sizeof(double); desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; node->nod_flags |= nod_double; return; case dtype_quad: desc->dsc_dtype = dtype_quad; desc->dsc_length = sizeof(SQUAD); desc->dsc_sub_type = 0; desc->dsc_flags = 0; node->nod_scale = desc->dsc_scale; node->nod_flags |= nod_quad; #ifdef NATIVE_QUAD return; #endif default: assert(FALSE); /* FALLINTO */ case dtype_sql_time: case dtype_sql_date: case dtype_timestamp: case dtype_blob: case dtype_array: /* break to error reporting code */ break; } break; case nod_prot_mask: case nod_null: case nod_agg_count: case nod_agg_count2: case nod_agg_count_distinct: case nod_count2: case nod_count: case nod_gen_id: case nod_lock_state: #ifdef PC_ENGINE case nod_lock_record: case nod_lock_relation: case nod_seek: case nod_seek_no_warn: case nod_crack: #endif desc->dsc_dtype = dtype_long; desc->dsc_length = sizeof(SLONG); desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; #ifdef PC_ENGINE case nod_begin_range: desc->dsc_dtype = dtype_text; desc->dsc_ttype = ttype_ascii; desc->dsc_scale = 0; desc->dsc_length = RANGE_NAME_LENGTH; desc->dsc_flags = 0; return; #endif case nod_field: { FMT format; USHORT id; id = (USHORT) node->nod_arg[e_fld_id]; format = CMP_format(tdbb, csb, (USHORT) node->nod_arg[e_fld_stream]); if (id >= format->fmt_count) { desc->dsc_dtype = dtype_null; desc->dsc_length = 0; desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; } else *desc = format->fmt_desc[id]; return; } case nod_scalar: { NOD sub; REL relation; USHORT id; FLD field; ARR array; sub = node->nod_arg[e_scl_field]; relation = csb->csb_rpt[(USHORT) sub-> nod_arg[e_fld_stream]].csb_relation; id = (USHORT) sub->nod_arg[e_fld_id]; field = MET_get_field(relation, id); if (!field || !(array = field->fld_array)) IBERROR(223); /* msg 223 argument of scalar operation must be an array */ *desc = array->arr_desc.ads_rpt[0].ads_desc; return; } case nod_divide: { DSC desc1, desc2; CMP_get_desc(tdbb, csb, node->nod_arg[0], &desc1); CMP_get_desc(tdbb, csb, node->nod_arg[1], &desc2); /* For compatibility with older versions of the product, we accept text types for division in blr_version4 (dialect <= 1) only. */ if (!(DTYPE_CAN_DIVIDE(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc1.dsc_dtype))) { if (desc1.dsc_dtype != dtype_null) break; /* error, dtype not supported by arithmetic */ } if (!(DTYPE_CAN_DIVIDE(desc2.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype))) { if (desc2.dsc_dtype != dtype_null) break; /* error, dtype not supported by arithmetic */ } } desc->dsc_dtype = DEFAULT_DOUBLE; desc->dsc_length = sizeof(double); desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; case nod_agg_average: case nod_agg_average_distinct: CMP_get_desc(tdbb, csb, node->nod_arg[0], desc); /* FALL INTO */ case nod_average: if (node->nod_type == nod_average) CMP_get_desc(tdbb, csb, node->nod_arg[e_stat_value], desc); if (!DTYPE_CAN_AVERAGE(desc->dsc_dtype)) { if (desc->dsc_dtype != dtype_null) break; } desc->dsc_dtype = DEFAULT_DOUBLE; desc->dsc_length = sizeof(double); desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; /* In 6.0, the AVERAGE of an exact numeric type is int64 with the same scale. Only AVERAGE on an approximate numeric type can return a double. */ case nod_agg_average2: case nod_agg_average_distinct2: CMP_get_desc(tdbb, csb, node->nod_arg[0], desc); /* In V6, the average of an exact type is computed in SINT64, rather than double as in prior releases. */ switch (dtype = desc->dsc_dtype) { case dtype_short: case dtype_long: case dtype_int64: desc->dsc_dtype = dtype_int64; desc->dsc_length = sizeof(SINT64); desc->dsc_sub_type = 0; desc->dsc_flags = 0; node->nod_scale = desc->dsc_scale; return; case dtype_null: desc->dsc_dtype = dtype_null; desc->dsc_length = 0; desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; default: if (!DTYPE_CAN_AVERAGE(desc->dsc_dtype)) break; desc->dsc_dtype = DEFAULT_DOUBLE; desc->dsc_length = sizeof(double); desc->dsc_scale = 0; desc->dsc_sub_type = 0; desc->dsc_flags = 0; node->nod_flags |= nod_double; return; } break; case nod_add: case nod_subtract: { DSC desc1, desc2; CMP_get_desc(tdbb, csb, node->nod_arg[0], &desc1); CMP_get_desc(tdbb, csb, node->nod_arg[1], &desc2); /* 92/05/29 DAVES - don't understand why this is done for ONLY dtype_text (eg: not dtype_cstring or dtype_varying) Doesn't appear to hurt. 94/04/04 DAVES - NOW I understand it! QLI will pass floating point values to the engine as text. All other numeric constants it turns into either integers or longs (with scale). */ dtype1 = desc1.dsc_dtype; if (dtype_int64 == dtype1) dtype1 = dtype_double; dtype2 = desc2.dsc_dtype; if (dtype_int64 == dtype2) dtype2 = dtype_double; if ((dtype1 == dtype_text) || (dtype2 == dtype_text)) dtype = MAX(MAX(dtype1, dtype2), (UCHAR) DEFAULT_DOUBLE); else dtype = MAX(dtype1, dtype2); switch (dtype) { case dtype_short: desc->dsc_dtype = dtype_long; desc->dsc_length = sizeof(SLONG); if (IS_DTYPE_ANY_TEXT(desc1.dsc_dtype) || IS_DTYPE_ANY_TEXT(desc2.dsc_dtype)) desc->dsc_scale = 0; else desc->dsc_scale = MIN(desc1.dsc_scale, desc2.dsc_scale); node->nod_scale = desc->dsc_scale; desc->dsc_sub_type = 0; desc->dsc_flags = 0; return; case dtype_sql_date: case dtype_sql_time: if (IS_DTYPE_ANY_TEXT(desc1.dsc_dtype) || IS_DTYPE_ANY_TEXT(desc2.dsc_dtype)) ERR_post(gds_expression_eval_err, 0); /* FALL INTO */ case dtype_timestamp: node->nod_flags |= nod_date; assert(DTYPE_IS_DATE(desc1.dsc_dtype) || DTYPE_IS_DATE(desc2.dsc_dtype)); if (COULD_BE_DATE(desc1) && COULD_BE_DATE(desc2)) { if (node->nod_type == nod_subtract) { /* - */ /* Legal permutations are: - - - -