/* * PROGRAM: JRD Access Method * MODULE: cmp.cpp * 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. * 2002.09.28 Dmitry Yemanov: Reworked internal_info stuff, enhanced * exception handling in SPs/triggers, * implemented ROWS_AFFECTED system variable * 2002.10.21 Nickolay Samofatov: Added support for explicit pessimistic locks * 2002.10.29 Nickolay Samofatov: Added support for savepoints * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port * 2002.10.30 Sean Leyne - Removed support for obsolete "PC_PLATFORM" define * 2003.10.05 Dmitry Yemanov: Added support for explicit cursors in PSQL */ #include "firebird.h" #include #include // abort #include "../jrd/common.h" #include "../jrd/ibase.h" #include "../jrd/jrd.h" #include "../jrd/sym.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/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/jrd_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.h" #include "../jrd/met_proto.h" #include "../jrd/mov_proto.h" #include "../jrd/dsc_proto.h" #include "../jrd/dbg_proto.h" // DBG_supervisor #include "../jrd/execute_statement.h" /* Pick up relation ids */ #include "../jrd/ini.h" /* 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 */ inline bool COULD_BE_DATE(const dsc desc) { return ((DTYPE_IS_DATE(desc.dsc_dtype)) || (desc.dsc_dtype <= dtype_any_text)); } //#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 */ inline bool IS_DATE_AND_TIME(const dsc d1, const dsc d2) { return (((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 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))) // size of req_rpb[0] const size_t REQ_TAIL = sizeof (Jrd::jrd_req::blk_repeat_type); const int MAP_LENGTH = 256; /* RITTER - changed HP10 to HPUX */ #if defined (HPUX) && defined (SUPERSERVER) const int MAX_RECURSION = 96; #else const int MAX_RECURSION = 128; #endif const int MAX_REQUEST_SIZE = 10485760; // 10 MB - just to be safe using namespace Jrd; static UCHAR* alloc_map(thread_db*, CompilerScratch*, USHORT); static jrd_nod* catenate_nodes(thread_db*, NodeStack&); static jrd_nod* copy(thread_db*, CompilerScratch*, jrd_nod*, UCHAR *, USHORT, jrd_nod*, bool); static void expand_view_nodes(thread_db*, CompilerScratch*, USHORT, NodeStack&, NOD_T); static void ignore_dbkey(thread_db*, CompilerScratch*, RecordSelExpr*, const jrd_rel*); static jrd_nod* make_defaults(thread_db*, CompilerScratch*, USHORT, jrd_nod*); static jrd_nod* make_validation(thread_db*, CompilerScratch*, USHORT); static jrd_nod* pass1(thread_db*, CompilerScratch*, jrd_nod*, jrd_rel*, USHORT, bool); static void pass1_erase(thread_db*, CompilerScratch*, jrd_nod*); static jrd_nod* pass1_expand_view(thread_db*, CompilerScratch*, USHORT, USHORT, bool); static void pass1_modify(thread_db*, CompilerScratch*, jrd_nod*); static RecordSelExpr* pass1_rse(thread_db*, CompilerScratch*, RecordSelExpr*, jrd_rel*, USHORT); static void pass1_source(thread_db*, CompilerScratch*, RecordSelExpr*, jrd_nod*, jrd_nod**, NodeStack&, jrd_rel*, USHORT); static jrd_nod* pass1_store(thread_db*, CompilerScratch*, jrd_nod*); static jrd_nod* pass1_update(thread_db*, CompilerScratch*, jrd_rel*, trig_vec*, USHORT, USHORT, SecurityClass::flags_t, jrd_rel*, USHORT); static jrd_nod* pass2(thread_db*, CompilerScratch*, jrd_nod* const, jrd_nod*); static void pass2_rse(thread_db*, CompilerScratch*, RecordSelExpr*); static jrd_nod* pass2_union(thread_db*, CompilerScratch*, jrd_nod*); static void plan_check(const CompilerScratch*, const RecordSelExpr*); static void plan_set(CompilerScratch*, RecordSelExpr*, jrd_nod*); static void post_procedure_access(thread_db*, CompilerScratch*, jrd_prc*); static RecordSource* post_rse(thread_db*, CompilerScratch*, RecordSelExpr*); static void post_trigger_access(CompilerScratch*, jrd_rel*, ExternalAccess::exa_act, jrd_rel*); static void process_map(thread_db*, CompilerScratch*, jrd_nod*, Format**); static bool stream_in_rse(USHORT, RecordSelExpr*); static SSHORT strcmp_space(const TEXT*, const TEXT*); static void build_external_access(thread_db* tdbb, ExternalAccessList& list, jrd_req* request); static void verify_trigger_access(thread_db* tdbb, jrd_rel* owner_relation, trig_vec* triggers, jrd_rel* view); #ifdef PC_ENGINE static USHORT base_stream(CompilerScratch*, jrd_nod**, bool); #endif #ifdef CMP_DEBUG IMPLEMENT_TRACE_ROUTINE(cmp_trace, "CMP") #endif bool CMP_clone_is_active(const jrd_req* request) { /************************************** * * C M P _ c l o n e _ i s _ a c t i v e * ************************************** * * Functional description * Determine if a request or any of its clones are active. * **************************************/ DEV_BLKCHK(request, type_req); if (request->req_flags & req_in_use) return true; // This should be const, but the iterator won't work then. vec* vector = request->req_sub_requests; if (vector) { for (vec::const_iterator sub_req = vector->begin(), end = vector->end(); sub_req < end; ++sub_req) { if (*sub_req && ((const jrd_req*)(*sub_req))->req_flags & req_in_use) return true; } } return false; } jrd_nod* CMP_clone_node(thread_db* tdbb, CompilerScratch* csb, jrd_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. * **************************************/ SET_TDBB(tdbb); DEV_BLKCHK(csb, type_csb); DEV_BLKCHK(node, type_nod); if (node->nod_type == nod_argument) { return node; } jrd_nod* clone = copy(tdbb, csb, node, NULL, 0, NULL, false); pass2(tdbb, csb, clone, 0); return clone; } inline void triggers_external_access(thread_db* tdbb, ExternalAccessList& list, trig_vec* vec) /************************************** * * t r i g g e r s _ e x t e r n a l _ a c c e s s * ************************************** * * Functional description * Invoke build_external_access for triggers in vector * **************************************/ { if (vec) { for (int i = 0; i < vec->getCount(); i++) { Trigger& t = (*vec)[i]; t.compile(tdbb); if (t.request) { build_external_access(tdbb, list, t.request); } } } } static void build_external_access(thread_db* tdbb, ExternalAccessList& list, jrd_req* request) { /************************************** * * b u i l d _ e x t e r n a l _ a c c e s s * ************************************** * * Functional description * Recursively walk external dependencies (procedures, triggers) for request to assemble full * list of requests it depends on * **************************************/ for (ExternalAccess *item = request->req_external.begin(); item < request->req_external.end(); item++) { size_t i; if (list.find(*item, i)) continue; list.insert(i, *item); // Add externals recursively if (item->exa_action == ExternalAccess::exa_procedure) { jrd_prc* prc = MET_lookup_procedure_id(tdbb, item->exa_prc_id, false, false, 0); if (prc && prc->prc_request) build_external_access(tdbb, list, prc->prc_request); } else { jrd_rel* relation = MET_lookup_relation_id(tdbb, item->exa_rel_id, false); if (!relation) continue; trig_vec *vec1, *vec2; switch(item->exa_action) { case ExternalAccess::exa_insert: vec1 = relation->rel_pre_store; vec2 = relation->rel_post_store; break; case ExternalAccess::exa_update: vec1 = relation->rel_pre_modify; vec2 = relation->rel_post_modify; break; case ExternalAccess::exa_delete: vec1 = relation->rel_pre_erase; vec2 = relation->rel_post_erase; break; default: continue; // should never happen, silence the compiler } triggers_external_access(tdbb, list, vec1); triggers_external_access(tdbb, list, vec2); } } } static void verify_trigger_access(thread_db* tdbb, jrd_rel* owner_relation, trig_vec* triggers, jrd_rel* view) { /************************************** * * v e r i f y _ t r i g g e r _ a c c e s s * ************************************** * * Functional description * Check that we have enough rights to access all resources this list of triggers touches * **************************************/ if (!triggers) { return; } for (int i = 0; i < triggers->getCount(); i++) { Trigger& t = (*triggers)[i]; t.compile(tdbb); if (!t.request) { continue; } for (const AccessItem* access = t.request->req_access.begin(); access < t.request->req_access.end(); access++) { // If this is not a system relation, we don't post access check if: // // - The table being checked is the owner of the trigger that's accessing it. // - The field being checked is owned by the same table than the trigger // that's accessing the field. // - Since the trigger name comes in the triggers vector of the table and each // trigger can be owned by only one table for now, we know for sure that // it's a trigger defined on our target table. if (!(owner_relation->rel_flags & REL_system)) { if (!strcmp(access->acc_type, object_table) && !strcmp(access->acc_name, owner_relation->rel_name)) { continue; } if (!strcmp(access->acc_type, object_column) && (MET_lookup_field(tdbb, owner_relation, access->acc_name, access->acc_security_name) >= 0 || MET_relation_default_class(tdbb, owner_relation->rel_name, access->acc_security_name))) { continue; } } // a direct access to an object from this trigger const SecurityClass* sec_class = SCL_get_class(access->acc_security_name); SCL_check_access(sec_class, (access->acc_view_id) ? access->acc_view_id : (view ? view->rel_id : 0), t.request->req_trg_name, 0, access->acc_mask, access->acc_type, access->acc_name); } } } void CMP_verify_access(thread_db* tdbb, jrd_req* request) { /************************************** * * C M P _ v e r i f y _ a c c e s s * ************************************** * * Functional description * Check that we have enough rights to access all resources this request touches including * resources it used indirectecty via procedures or triggers * **************************************/ ExternalAccessList external; build_external_access(tdbb, external, request); for (ExternalAccess* item = external.begin(); item < external.end(); item++) { if (item->exa_action == ExternalAccess::exa_procedure) { jrd_prc* prc = MET_lookup_procedure_id(tdbb, item->exa_prc_id, false, false, 0); if (!prc->prc_request) continue; for (const AccessItem* access = prc->prc_request->req_access.begin(); access < prc->prc_request->req_access.end(); access++) { const SecurityClass* sec_class = SCL_get_class(access->acc_security_name); SCL_check_access(sec_class, access->acc_view_id, NULL, prc->prc_name.c_str(), access->acc_mask, access->acc_type, access->acc_name); } } else { jrd_rel* relation = MET_lookup_relation_id(tdbb, item->exa_rel_id, false); jrd_rel* view = NULL; if (item->exa_view_id) view = MET_lookup_relation_id(tdbb, item->exa_view_id, false); if (!relation) continue; switch(item->exa_action) { case ExternalAccess::exa_insert: verify_trigger_access(tdbb, relation, relation->rel_pre_store, view); verify_trigger_access(tdbb, relation, relation->rel_post_store, view); break; case ExternalAccess::exa_update: verify_trigger_access(tdbb, relation, relation->rel_pre_modify, view); verify_trigger_access(tdbb, relation, relation->rel_post_modify, view); break; case ExternalAccess::exa_delete: verify_trigger_access(tdbb, relation, relation->rel_pre_erase, view); verify_trigger_access(tdbb, relation, relation->rel_post_erase, view); break; default: continue; // should never happen, silence the compiler } } } for (const AccessItem* access = request->req_access.begin(); access < request->req_access.end(); access++) { const SecurityClass* sec_class = SCL_get_class(access->acc_security_name); SCL_check_access(sec_class, access->acc_view_id, NULL, NULL, access->acc_mask, access->acc_type, access->acc_name); } } jrd_req* CMP_clone_request(thread_db* tdbb, jrd_req* request, USHORT level, bool 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. * **************************************/ DEV_BLKCHK(request, type_req); SET_TDBB(tdbb); // find the request if we've got it if (!level) { return request; } jrd_req* clone; vec* vector = request->req_sub_requests; if (vector && level < vector->count() && (clone = (jrd_req*) (*vector)[level])) { return clone; } if (validate) { jrd_prc* procedure = request->req_procedure; if (procedure) { const TEXT* prc_sec_name = (procedure->prc_security_name.hasData() ? procedure->prc_security_name.c_str() : NULL); const SecurityClass* sec_class = SCL_get_class(prc_sec_name); SCL_check_access(sec_class, 0, 0, 0, SCL_execute, object_procedure, procedure->prc_name.c_str()); } CMP_verify_access(tdbb, request); } // we need to clone the request - find someplace to put it vector = request->req_sub_requests = vec::newVector(*request->req_pool, request->req_sub_requests, level + 1); // clone the request const USHORT n = (USHORT) ((request->req_impure_size - REQ_SIZE + REQ_TAIL - 1) / REQ_TAIL); clone = FB_NEW_RPT(*request->req_pool, n) jrd_req(request->req_pool); (*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; clone->req_last_xcp = request->req_last_xcp; // We are cloning full lists here, not assigning pointers clone->req_invariants = request->req_invariants; clone->req_fors = request->req_fors; record_param* rpb1 = clone->req_rpb; const record_param* const end = rpb1 + clone->req_count; for (const record_param* 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; } jrd_req* CMP_compile(USHORT blr_length, const 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(JRD_get_thread_data(), blr, internal_flag); } jrd_req* CMP_compile2(thread_db* tdbb, const UCHAR* blr, USHORT internal_flag) { /************************************** * * C M P _ c o m p i l e 2 * ************************************** * * Functional description * Compile a BLR request. * **************************************/ jrd_req* request = 0; SET_TDBB(tdbb); JrdMemoryPool* old_pool = tdbb->tdbb_default; // 26.09.2002 Nickolay Samofatov: default memory pool will become statement pool // and will be freed by CMP_release JrdMemoryPool* new_pool = JrdMemoryPool::createPool(); tdbb->tdbb_default = new_pool; try { CompilerScratch* csb = PAR_parse(tdbb, blr, internal_flag); request = CMP_make_request(tdbb, csb); if (internal_flag) { request->req_flags |= req_internal; } CMP_verify_access(tdbb, request); delete csb; tdbb->tdbb_default = old_pool; } catch (const std::exception& ex) { Firebird::stuff_exception(tdbb->tdbb_status_vector, ex); tdbb->tdbb_default = old_pool; if (request) { CMP_release(tdbb, request); } else if (new_pool) { JrdMemoryPool::deletePool(new_pool); } ERR_punt(); } return request; } CompilerScratch::csb_repeat* CMP_csb_element(CompilerScratch* csb, USHORT element) { /************************************** * * C M P _ c s b _ e l e m e n t * ************************************** * * Functional description * Find tail element of compiler scratch block. If the csb isn't big * enough, extend it. * **************************************/ DEV_BLKCHK(csb, type_csb); CompilerScratch::csb_repeat empty_item; while (element >= csb->csb_rpt.getCount()) { csb->csb_rpt.add(empty_item); } return &csb->csb_rpt[element]; } void CMP_expunge_transaction(jrd_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. * **************************************/ DEV_BLKCHK(transaction, type_tra); for (jrd_req* request = transaction->tra_attachment->att_requests; request; request = request->req_request) { if (request->req_transaction == transaction) { request->req_transaction = NULL; } vec* vector = request->req_sub_requests; if (vector) { vec::iterator sub, end; for (sub = vector->begin(), end = vector->end(); sub < end; sub++) { if (*sub && ((jrd_req*)(*sub))->req_transaction == transaction) { ((jrd_req*)(*sub))->req_transaction = NULL; } } } } } jrd_req* CMP_find_request(thread_db* 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. * **************************************/ SET_TDBB(tdbb); Database* 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); jrd_req* request; if ((which == IRQ_REQUESTS && !(request = (jrd_req*) REQUEST(id))) || (which == DYN_REQUESTS && !(request = (jrd_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 (USHORT n = 1; true; n++) { if (n > MAX_RECURSION) { THD_MUTEX_UNLOCK(dbb->dbb_mutexes + DBB_MUTX_cmp_clone); ERR_post(isc_no_meta_update, isc_arg_gds, isc_req_depth_exceeded, isc_arg_number, (SLONG) MAX_RECURSION, 0); // Msg363 "request depth exceeded. (Recursive definition?)" } jrd_req* 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 CMP_fini(thread_db* tdbb) { /************************************** * * C M P _ f i n i * ************************************** * * Functional description * Get rid of resource locks during shutdown. * **************************************/ SET_TDBB(tdbb); CMP_shutdown_database(tdbb); } Format* CMP_format(thread_db* tdbb, CompilerScratch* csb, USHORT stream) { /************************************** * * C M P _ f o r m a t * ************************************** * * Functional description * Pick up a format for a stream. * **************************************/ SET_TDBB(tdbb); DEV_BLKCHK(csb, type_csb); CompilerScratch::csb_repeat* 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 NULL; } void CMP_get_desc(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node, DSC * desc) { /************************************** * * C M P _ g e t _ d e s c * ************************************** * * Functional description * Compute descriptor for value expression. * **************************************/ USHORT dtype = dtype_unknown; 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_unknown: desc->dsc_dtype = dtype_unknown; 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: fb_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_unknown: desc->dsc_dtype = dtype_unknown; 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: fb_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: { const USHORT id = (USHORT) (IPTR) node->nod_arg[e_fld_id]; const Format* format = CMP_format(tdbb, csb, (USHORT) (IPTR) node->nod_arg[e_fld_stream]); if (id >= format->fmt_count) { desc->dsc_dtype = dtype_unknown; 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: { jrd_nod* sub = node->nod_arg[e_scl_field]; jrd_rel* relation = csb->csb_rpt[(USHORT)(IPTR) sub-> nod_arg[e_fld_stream]].csb_relation; const USHORT id = (USHORT)(IPTR) sub->nod_arg[e_fld_id]; const jrd_fld* field = MET_get_field(relation, id); const ArrayField* array; if (!field || !(array = field->fld_array)) { IBERROR(223); // msg 223 argument of scalar operation must be an array } *desc = array->arr_desc.iad_rpt[0].iad_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_unknown) { 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_unknown) { 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_unknown) { 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_unknown: desc->dsc_dtype = dtype_unknown; 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). */ USHORT dtype1 = desc1.dsc_dtype; if (dtype_int64 == dtype1) { dtype1 = dtype_double; } USHORT 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 (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_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 (DTYPE_IS_TEXT(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc2.dsc_dtype)) { ERR_post(isc_expression_eval_err, 0); } // FALL INTO case dtype_timestamp: node->nod_flags |= nod_date; fb_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: - - - -