/* * 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 * Adriano dos Santos Fernandes */ #include "firebird.h" #include #include // abort #include "../jrd/common.h" #include "../jrd/ibase.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/lck.h" #include "../jrd/irq.h" #include "../jrd/drq.h" #include "../jrd/intl.h" #include "../jrd/btr.h" #include "../jrd/gdsassert.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/intl_proto.h" #include "../jrd/jrd_proto.h" #include "../jrd/lck_proto.h" #include "../jrd/opt_proto.h" #include "../jrd/par_proto.h" #include "../jrd/scl_proto.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" #include "../jrd/Optimizer.h" #include "../jrd/DataTypeUtil.h" #include "../jrd/SysFunction.h" /* Pick up relation ids */ #include "../jrd/ini.h" #include "../common/classes/auto.h" #include "../common/utils_proto.h" #include "../dsql/Nodes.h" using Firebird::AutoSetRestore; /* Firebird 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; using namespace Firebird; static UCHAR* alloc_map(thread_db*, CompilerScratch*, USHORT); static jrd_nod* catenate_nodes(thread_db*, NodeStack&); static jrd_nod* convertNeqAllToNotAny(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node); 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 void mark_variant(thread_db* tdbb, CompilerScratch* csb, USHORT stream); 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*); static void pass1_source(thread_db*, CompilerScratch*, RecordSelExpr*, jrd_nod*, jrd_nod**, NodeStack&); static bool pass1_store(thread_db*, CompilerScratch*, jrd_nod*); static jrd_nod* pass1_update(thread_db*, CompilerScratch*, jrd_rel*, const trig_vec*, USHORT, USHORT, SecurityClass::flags_t, jrd_rel*, USHORT); static void pass2_rse(thread_db*, CompilerScratch*, RecordSelExpr*); static jrd_nod* pass2_union(thread_db*, CompilerScratch*, jrd_nod*); static jrd_nod* pass2_validation(thread_db*, CompilerScratch*, const Item&); 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 SSHORT strcmp_space(const char*, const char*); static bool stream_in_rse(USHORT, const RecordSelExpr*); 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 CMP_DEBUG #include 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; 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 && (*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 node. * **************************************/ SET_TDBB(tdbb); DEV_BLKCHK(csb, type_csb); DEV_BLKCHK(node, type_nod); return copy(tdbb, csb, node, NULL, 0, NULL, false); } jrd_nod* CMP_clone_node_opt(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node) { /************************************** * * C M P _ c l o n e _ n o d e _ o p t * ************************************** * * 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); CMP_pass2(tdbb, csb, clone, 0); return clone; } inline void triggers_external_access(thread_db* tdbb, ExternalAccessList& list, trig_vec* tvec) /************************************** * * 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 (tvec) { for (size_t i = 0; i < tvec->getCount(); i++) { Trigger& t = (*tvec)[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; } SET_TDBB(tdbb); for (size_t 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) && (owner_relation->rel_name == access->acc_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(tdbb, access->acc_security_name.c_str()); SCL_check_access(tdbb, sec_class, (access->acc_view_id) ? access->acc_view_id : (view ? view->rel_id : 0), t.request->req_trg_name, NULL, access->acc_mask, access->acc_type, access->acc_name, access->acc_r_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 * **************************************/ SET_TDBB(tdbb); 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(tdbb, access->acc_security_name.c_str()); SCL_check_access(tdbb, sec_class, access->acc_view_id, NULL, prc->prc_name, access->acc_mask, access->acc_type, access->acc_name, access->acc_r_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 } } } // Inherit privileges of caller stored procedure or trigger if and only if // this request is called immediately by caller (check for empty req_caller). // Currently (in v2.5) this rule will work for EXECUTE STATEMENT only, as // tra_callback_count incremented only by it. // When external SP's will be introduced we need to decide if they also can // inherit caller's privileges jrd_tra* transaction = tdbb->getTransaction(); const jrd_req* exec_stmt_caller = (transaction && transaction->tra_callback_count && !request->req_caller) ? transaction->tra_callback_caller : NULL; for (const AccessItem* access = request->req_access.begin(); access < request->req_access.end(); access++) { const SecurityClass* sec_class = SCL_get_class(tdbb, access->acc_security_name.c_str()); Firebird::MetaName trgName(exec_stmt_caller ? exec_stmt_caller->req_trg_name : NULL); Firebird::MetaName prcName(exec_stmt_caller && exec_stmt_caller->req_procedure ? exec_stmt_caller->req_procedure->prc_name : NULL); SCL_check_access(tdbb, sec_class, access->acc_view_id, trgName, prcName, access->acc_mask, access->acc_type, access->acc_name, access->acc_r_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); Database* const dbb = tdbb->getDatabase(); Attachment* const attachment = tdbb->getAttachment(); fb_assert(dbb); // 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 = (*vector)[level])) { return clone; } if (validate) { jrd_prc* procedure = request->req_procedure; if (procedure) { const TEXT* prc_sec_name = (procedure->prc_security_name.length() > 0 ? procedure->prc_security_name.c_str() : NULL); const SecurityClass* sec_class = SCL_get_class(tdbb, prc_sec_name); SCL_check_access(tdbb, sec_class, 0, NULL, NULL, SCL_execute, object_procedure, procedure->prc_name); } CMP_verify_access(tdbb, request); } MemoryPool* const pool = request->req_pool; // we need to clone the request - find someplace to put it vector = request->req_sub_requests = vec::newVector(*pool, request->req_sub_requests, level + 1); // clone the request const size_t n = (request->req_impure_size - REQ_SIZE + REQ_TAIL - 1) / REQ_TAIL; clone = FB_NEW_RPT(*pool, n) jrd_req(pool, &dbb->dbb_memory_stats); (*vector)[level] = clone; clone->req_attachment = attachment; clone->req_count = request->req_count; 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_procedure = request->req_procedure; clone->req_flags = request->req_flags & REQ_FLAGS_CLONE_MASK; clone->req_last_xcp = request->req_last_xcp; clone->req_id = fb_utils::genUniqueId(); // We are cloning full lists here, not assigning pointers clone->req_invariants = request->req_invariants; clone->req_fors = request->req_fors; clone->req_exec_sta = request->req_exec_sta; clone->req_map_field_info.assign(request->req_map_field_info); 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_compile2(thread_db* tdbb, const UCHAR* blr, USHORT internal_flag, USHORT dbginfo_length, const UCHAR* dbginfo) { /************************************** * * C M P _ c o m p i l e 2 * ************************************** * * Functional description * Compile a BLR request. * **************************************/ jrd_req* request = NULL; SET_TDBB(tdbb); Database* const dbb = tdbb->getDatabase(); // 26.09.2002 Nickolay Samofatov: default memory pool will become statement pool // and will be freed by CMP_release MemoryPool* const new_pool = dbb->createPool(); try { Jrd::ContextPoolHolder context(tdbb, new_pool); CompilerScratch* csb = PAR_parse(tdbb, blr, internal_flag, dbginfo_length, dbginfo); request = CMP_make_request(tdbb, csb, internal_flag); new_pool->setStatsGroup(request->req_memory_stats); if (internal_flag) { request->req_flags |= req_internal; } CMP_verify_access(tdbb, request); delete csb; } catch (const Firebird::Exception& ex) { Firebird::stuff_exception(tdbb->tdbb_status_vector, ex); if (request) CMP_release(tdbb, request); else dbb->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]; } 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->getDatabase(); CHECK_DBB(dbb); // if the request hasn't been compiled or isn't active, // there're nothing to do Database::CheckoutLockGuard guard(dbb, dbb->dbb_cmp_clone_mutex); jrd_req* request; if ((which == IRQ_REQUESTS && !(request = REQUEST(id))) || (which == DYN_REQUESTS && !(request = DYN_REQUEST(id))) || !(request->req_flags & (req_active | req_reserved))) { if (request) { request->req_flags |= req_reserved; } return request; } // Request exists and is in use. Look for clones until we find // one that is available. for (int n = 1; true; n++) { if (n > MAX_RECURSION) { ERR_post(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_req_depth_exceeded) << Arg::Num(MAX_RECURSION)); // 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; 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); } 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: 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: 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 SCROLLABLE_CURSORS case nod_seek: #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; case nod_field: { const USHORT stream = (USHORT) (IPTR) node->nod_arg[e_fld_stream]; const USHORT id = (USHORT) (IPTR) node->nod_arg[e_fld_id]; const Format* format = CMP_format(tdbb, csb, 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]; // fix UNICODE_FSS wrong length used in system tables jrd_rel* relation = csb->csb_rpt[stream].csb_relation; if (relation && (relation->rel_flags & REL_system) && desc->isText() && desc->getCharSet() == CS_UNICODE_FSS) { USHORT adjust = 0; if (desc->dsc_dtype == dtype_varying) adjust = sizeof(USHORT); else if (desc->dsc_dtype == dtype_cstring) adjust = 1; desc->dsc_length -= adjust; desc->dsc_length *= 3; desc->dsc_length += adjust; } } 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; if (array->arr_desc.iad_dimensions > MAX_ARRAY_DIMENSIONS) IBERROR(306); // Found array data type with more than 16 dimensions 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_IS_NUMERIC(desc1.dsc_dtype) || DTYPE_IS_TEXT(desc1.dsc_dtype))) { if (desc1.dsc_dtype != dtype_unknown) { break; // error, dtype not supported by arithmetic } } if (!(DTYPE_IS_NUMERIC(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_IS_NUMERIC(desc->dsc_dtype) || DTYPE_IS_TEXT(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_IS_NUMERIC(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_agg_list: case nod_agg_list_distinct: CMP_get_desc(tdbb, csb, node->nod_arg[0], desc); desc->makeBlob(desc->getBlobSubType(), desc->getTextType()); return; 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(Arg::Gds(isc_expression_eval_err)); } // 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: - - - -