/* * 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): ______________________________________. */ #include "firebird.h" #include "../jrd/common.h" #include "../jrd/gdsassert.h" #include "../jrd/flags.h" #include "../jrd/ibase.h" #include "../jrd/jrd.h" #include "../jrd/val.h" #include "../jrd/irq.h" #include "../jrd/tra.h" #include "../jrd/lck.h" #include "../jrd/req.h" #include "../jrd/exe.h" #include "../jrd/blb.h" #include "../jrd/met.h" #include "../jrd/align.h" #include "../jrd/blb_proto.h" #include "../jrd/cmp_proto.h" #include "../jrd/dsc_proto.h" #include "../jrd/evl_proto.h" #include "../jrd/exe_proto.h" #include "../jrd/flu_proto.h" #include "../jrd/fun_proto.h" #include "../jrd/lck_proto.h" #include "../jrd/met_proto.h" #include "../jrd/mov_proto.h" #include "../jrd/par_proto.h" #include "../jrd/vio_proto.h" #include "../jrd/thread_proto.h" #include "../common/utils_proto.h" #include "../jrd/DebugInterface.h" #include "../jrd/Function.h" using namespace Firebird; using namespace Jrd; DATABASE DB = FILENAME "ODS.RDB"; const char* const Function::EXCEPTION_MESSAGE = "The user defined function: \t%s\n\t referencing" " entrypoint: \t%s\n\t in module: \t%s\n\tcaused the fatal exception:"; Function* Function::lookup(thread_db* tdbb, USHORT id, bool return_deleted, bool noscan, USHORT flags) { Database* const dbb = tdbb->getDatabase(); Function* check_function = NULL; Function* function = (id < dbb->dbb_functions.getCount()) ? dbb->dbb_functions[id] : NULL; if (function && function->getId() == id && !(function->fun_flags & FUN_being_scanned) && ((function->fun_flags & FUN_scanned) || noscan) && !(function->fun_flags & FUN_being_altered) && (!(function->fun_flags & FUN_obsolete) || return_deleted)) { if (!(function->fun_flags & FUN_check_existence)) { return function; } check_function = function; LCK_lock(tdbb, check_function->fun_existence_lock, LCK_SR, LCK_WAIT); } // We need to look up the function in RDB$FUNCTIONS function = NULL; jrd_req* request = CMP_find_request(tdbb, irq_l_fun_id, IRQ_REQUESTS); FOR(REQUEST_HANDLE request) X IN RDB$FUNCTIONS WITH X.RDB$FUNCTION_ID EQ id if (!REQUEST(irq_l_fun_id)) REQUEST(irq_l_fun_id) = request; function = loadMetadata(tdbb, X.RDB$FUNCTION_ID, noscan, flags); END_FOR; if (!REQUEST(irq_l_fun_id)) REQUEST(irq_l_fun_id) = request; if (check_function) { check_function->fun_flags &= ~FUN_check_existence; if (check_function != function) { LCK_release(tdbb, check_function->fun_existence_lock); check_function->fun_flags |= FUN_obsolete; } } return function; } Function* Function::lookup(thread_db* tdbb, const QualifiedName& name, bool noscan) { Database* const dbb = tdbb->getDatabase(); Function* check_function = NULL; // See if we already know the function by name for (Function** iter = dbb->dbb_functions.begin(); iter < dbb->dbb_functions.end(); ++iter) { Function* const function = *iter; if (function && !(function->fun_flags & FUN_obsolete) && ((function->fun_flags & FUN_scanned) || noscan) && !(function->fun_flags & FUN_being_scanned) && !(function->fun_flags & FUN_being_altered)) { if (function->getName() == name) { if (function->fun_flags & FUN_check_existence) { check_function = function; LCK_lock(tdbb, check_function->fun_existence_lock, LCK_SR, LCK_WAIT); break; } return function; } } } // We need to look up the function in RDB$FUNCTIONS Function* function = NULL; jrd_req* request = CMP_find_request(tdbb, irq_l_fun_name, IRQ_REQUESTS); FOR(REQUEST_HANDLE request) X IN RDB$FUNCTIONS WITH X.RDB$FUNCTION_NAME EQ name.identifier.c_str() AND X.RDB$PACKAGE_NAME EQUIV NULLIF(name.package.c_str(), '') if (!REQUEST(irq_l_fun_name)) REQUEST(irq_l_fun_name) = request; function = loadMetadata(tdbb, X.RDB$FUNCTION_ID, noscan, 0); END_FOR; if (!REQUEST(irq_l_fun_name)) REQUEST(irq_l_fun_name) = request; if (check_function) { check_function->fun_flags &= ~FUN_check_existence; if (check_function != function) { LCK_release(tdbb, check_function->fun_existence_lock); check_function->fun_flags |= FUN_obsolete; } } return function; } Function* Function::loadMetadata(thread_db* tdbb, USHORT id, bool noscan, USHORT flags) { Database* const dbb = tdbb->getDatabase(); if (id >= dbb->dbb_functions.getCount()) { dbb->dbb_functions.grow(id + 1); } Database::CheckoutLockGuard guard(dbb, dbb->dbb_meta_mutex); Function* function = dbb->dbb_functions[id]; if (function && !(function->fun_flags & FUN_obsolete)) { // Make sure FUN_being_scanned and FUN_scanned are not set at the same time fb_assert(!(function->fun_flags & FUN_being_scanned) || !(function->fun_flags & FUN_scanned)); if ((function->fun_flags & FUN_being_scanned) || (function->fun_flags & FUN_scanned)) { return function; } } if (!function) { function = FB_NEW(*dbb->dbb_permanent) Function(*dbb->dbb_permanent); } try { function->fun_flags |= (FUN_being_scanned | flags); function->fun_flags &= ~FUN_obsolete; function->setId(id); dbb->dbb_functions[id] = function; if (!function->fun_existence_lock) { Lock* const lock = FB_NEW_RPT(*dbb->dbb_permanent, 0) Lock; function->fun_existence_lock = lock; lock->lck_parent = dbb->dbb_lock; lock->lck_dbb = dbb; lock->lck_key.lck_long = function->getId(); lock->lck_length = sizeof(lock->lck_key.lck_long); lock->lck_type = LCK_fun_exist; lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type); lock->lck_object = function; lock->lck_ast = blockingAst; } LCK_lock(tdbb, function->fun_existence_lock, LCK_SR, LCK_WAIT); if (!noscan) { bool valid_blr = true; jrd_req* request_fun = CMP_find_request(tdbb, irq_l_functions, IRQ_REQUESTS); fun_repeat temp[MAX_UDF_ARGUMENTS + 1]; FOR(REQUEST_HANDLE request_fun) X IN RDB$FUNCTIONS WITH X.RDB$FUNCTION_ID EQ id if (!REQUEST(irq_l_functions)) REQUEST(irq_l_functions) = request_fun; function->setName(QualifiedName(X.RDB$FUNCTION_NAME, (X.RDB$PACKAGE_NAME.NULL ? NULL : X.RDB$PACKAGE_NAME))); if (!X.RDB$SECURITY_CLASS.NULL) { function->setSecurityName(X.RDB$SECURITY_CLASS); } else if (!X.RDB$PACKAGE_NAME.NULL) { AutoCacheRequest requestHandle(tdbb, irq_l_procedure_pkg_class, IRQ_REQUESTS); FOR (REQUEST_HANDLE requestHandle) PKG IN RDB$PACKAGES WITH PKG.RDB$PACKAGE_NAME EQ X.RDB$PACKAGE_NAME if (!PKG.RDB$SECURITY_CLASS.NULL) { function->setSecurityName(PKG.RDB$SECURITY_CLASS); } END_FOR } size_t count = 0; memset(temp, 0, sizeof(temp)); ULONG length = 0; function->setUndefined(false); function->fun_inputs = 0; function->fun_defaults = 0; jrd_req* request_arg = CMP_find_request(tdbb, irq_l_args, IRQ_REQUESTS); FOR(REQUEST_HANDLE request_arg) Y IN RDB$FUNCTION_ARGUMENTS WITH Y.RDB$FUNCTION_NAME EQ function->getName().identifier.c_str() AND Y.RDB$PACKAGE_NAME EQUIV NULLIF(function->getName().package.c_str(), '') SORTED BY Y.RDB$ARGUMENT_POSITION if (!REQUEST(irq_l_args)) REQUEST(irq_l_args) = request_arg; if (Y.RDB$ARGUMENT_POSITION != X.RDB$RETURN_ARGUMENT) function->fun_inputs++; fun_repeat* const tail = temp + Y.RDB$ARGUMENT_POSITION; tail->fun_mechanism = (FUN_T) Y.RDB$MECHANISM; Parameter* const parameter = FB_NEW(*dbb->dbb_permanent) Parameter(*dbb->dbb_permanent); tail->fun_parameter = parameter; parameter->prm_number = Y.RDB$ARGUMENT_POSITION; parameter->prm_name = Y.RDB$ARGUMENT_NAME.NULL ? "" : Y.RDB$ARGUMENT_NAME; parameter->prm_nullable = Y.RDB$NULL_FLAG.NULL || Y.RDB$NULL_FLAG == 0; parameter->prm_mechanism = Y.RDB$ARGUMENT_MECHANISM.NULL ? prm_mech_normal : (prm_mech_t) Y.RDB$ARGUMENT_MECHANISM; const SSHORT collation_id_null = Y.RDB$COLLATION_ID.NULL; const SSHORT collation_id = Y.RDB$COLLATION_ID; SSHORT default_value_null = Y.RDB$DEFAULT_VALUE.NULL; bid default_value = Y.RDB$DEFAULT_VALUE; if (!Y.RDB$FIELD_SOURCE.NULL) { parameter->prm_field_source = Y.RDB$FIELD_SOURCE; jrd_req* request_arg_fld = CMP_find_request(tdbb, irq_l_arg_fld, IRQ_REQUESTS); FOR(REQUEST_HANDLE request_arg_fld) F IN RDB$FIELDS WITH F.RDB$FIELD_NAME = Y.RDB$FIELD_SOURCE if (!REQUEST(irq_l_arg_fld)) REQUEST(irq_l_arg_fld) = request_arg_fld; DSC_make_descriptor(¶meter->prm_desc, F.RDB$FIELD_TYPE, F.RDB$FIELD_SCALE, F.RDB$FIELD_LENGTH, F.RDB$FIELD_SUB_TYPE, F.RDB$CHARACTER_SET_ID, (collation_id_null ? F.RDB$COLLATION_ID : collation_id)); if (default_value_null && fb_utils::implicit_domain(F.RDB$FIELD_NAME)) { default_value_null = F.RDB$DEFAULT_VALUE.NULL; default_value = F.RDB$DEFAULT_VALUE; } END_FOR if (!REQUEST(irq_l_arg_fld)) REQUEST(irq_l_arg_fld) = request_arg_fld; } else { DSC_make_descriptor(¶meter->prm_desc, Y.RDB$FIELD_TYPE, Y.RDB$FIELD_SCALE, Y.RDB$FIELD_LENGTH, Y.RDB$FIELD_SUB_TYPE, Y.RDB$CHARACTER_SET_ID, (collation_id_null ? 0 : collation_id)); } if (Y.RDB$ARGUMENT_POSITION != X.RDB$RETURN_ARGUMENT && !default_value_null) { function->fun_defaults++; MemoryPool* const csb_pool = dbb->createPool(); Jrd::ContextPoolHolder context(tdbb, csb_pool); CompilerScratch* csb = CompilerScratch::newCsb(*tdbb->getDefaultPool(), 5); jrd_req* request = NULL; try { parameter->prm_default_value = MET_parse_blob(tdbb, NULL, &default_value, &csb, &request, false); } catch (const Exception&) { delete csb; if (request) { CMP_release(tdbb, request); } else { dbb->deletePool(csb_pool); } throw; // an explicit error message would be better } delete csb; } if (tail->fun_parameter->prm_desc.dsc_dtype == dtype_cstring) tail->fun_parameter->prm_desc.dsc_length++; length += (tail->fun_parameter->prm_desc.dsc_dtype == dtype_blob) ? sizeof(udf_blob) : FB_ALIGN(tail->fun_parameter->prm_desc.dsc_length, FB_DOUBLE_ALIGN); count = MAX(count, size_t(Y.RDB$ARGUMENT_POSITION)); END_FOR; if (!REQUEST(irq_l_args)) REQUEST(irq_l_args) = request_arg; function->fun_return_arg = X.RDB$RETURN_ARGUMENT; function->fun_temp_length = length; function->fun_args.resize(count + 1); memcpy(function->fun_args.begin(), temp, function->fun_args.getCount() * sizeof(fun_repeat)); // Prepare the exception message to be used in case this function ever // causes an exception. This is done at this time to save us from preparing // (thus allocating) this message every time the function is called. function->fun_exception_message.printf(EXCEPTION_MESSAGE, function->getName().toString().c_str(), X.RDB$ENTRYPOINT, X.RDB$MODULE_NAME); if (!X.RDB$LEGACY_FLAG.NULL) { function->fun_legacy = (X.RDB$LEGACY_FLAG != 0); } if (!X.RDB$INVARIANT_FLAG.NULL) { function->fun_invariant = (X.RDB$INVARIANT_FLAG != 0); } if (!X.RDB$ENGINE_NAME.NULL) { fb_assert(!function->fun_legacy); function->fun_entrypoint = NULL; function->setRequest(NULL); HalfStaticArray body; if (!X.RDB$FUNCTION_SOURCE.NULL) { blb* const blob = BLB_open(tdbb, dbb->dbb_sys_trans, &X.RDB$FUNCTION_SOURCE); const ULONG len = BLB_get_data(tdbb, blob, body.getBuffer(blob->blb_length + 1), blob->blb_length + 1); body[MIN(blob->blb_length, len)] = 0; } else { body.getBuffer(1)[0] = 0; } function->fun_external = dbb->dbb_extManager.makeFunction( tdbb, function, X.RDB$ENGINE_NAME, (X.RDB$ENTRYPOINT.NULL ? "" : X.RDB$ENTRYPOINT), (char*) body.begin()); } else if (!X.RDB$FUNCTION_BLR.NULL) { fb_assert(!function->fun_legacy); function->fun_external = NULL; function->fun_entrypoint = NULL; MemoryPool* const csb_pool = dbb->createPool(); Jrd::ContextPoolHolder context(tdbb, csb_pool); CompilerScratch* const csb = CompilerScratch::newCsb(*tdbb->getDefaultPool(), 5); if (!X.RDB$DEBUG_INFO.NULL) { DBG_parse_debug_info(tdbb, &X.RDB$DEBUG_INFO, csb->csb_dbg_info); } try { function->parseBlr(tdbb, &X.RDB$FUNCTION_BLR, csb); } catch (const Exception&) { delete csb; if (function->getRequest()) { CMP_release(tdbb, function->getRequest()); function->setRequest(NULL); } else { dbb->deletePool(csb_pool); } const string name = function->getName().toString(); status_exception::raise(Arg::Gds(isc_bad_fun_BLR) << Arg::Str(name)); } function->getRequest()->req_function = function; delete csb; } else { function->fun_external = NULL; function->setRequest(NULL); if (!X.RDB$MODULE_NAME.NULL && !X.RDB$ENTRYPOINT.NULL) { fb_assert(function->fun_legacy); function->fun_entrypoint = Module::lookup(X.RDB$MODULE_NAME, X.RDB$ENTRYPOINT, dbb->dbb_modules); // Could not find a function with given MODULE, ENTRYPOINT. // Try the list of internally implemented functions. if (!function->fun_entrypoint) { function->fun_entrypoint = BUILTIN_entrypoint(X.RDB$MODULE_NAME, X.RDB$ENTRYPOINT); } } else { function->fun_entrypoint = NULL; function->setUndefined(true); } } if (X.RDB$VALID_BLR.NULL || X.RDB$VALID_BLR == FALSE) { valid_blr = false; } function->fun_flags |= FUN_scanned; END_FOR; if (!REQUEST(irq_l_functions)) REQUEST(irq_l_functions) = request_fun; if (!(dbb->dbb_flags & DBB_read_only) && !valid_blr) { // if the BLR was marked as invalid but the function was compiled, // mark the BLR as valid jrd_req* request_set_valid = NULL; FOR(REQUEST_HANDLE request_set_valid) F IN RDB$FUNCTIONS WITH F.RDB$FUNCTION_ID EQ function->getId() MODIFY F USING F.RDB$VALID_BLR = TRUE; F.RDB$VALID_BLR.NULL = FALSE; END_MODIFY; END_FOR; CMP_release(tdbb, request_set_valid); } } // Make sure that it is really being scanned fb_assert(function->fun_flags & FUN_being_scanned); function->fun_flags &= ~FUN_being_scanned; } // try catch (const Exception&) { function->fun_flags &= ~(FUN_being_scanned | FUN_scanned); if (function->fun_existence_lock) { LCK_release(tdbb, function->fun_existence_lock); function->fun_existence_lock = NULL; } throw; } return function; } int Function::blockingAst(void* ast_object) { Function* const function = static_cast(ast_object); try { Database* const dbb = function->fun_existence_lock->lck_dbb; Database::SyncGuard dsGuard(dbb, true); ThreadContextHolder tdbb; tdbb->setDatabase(dbb); tdbb->setAttachment(function->fun_existence_lock->lck_attachment); Jrd::ContextPoolHolder context(tdbb, 0); LCK_release(tdbb, function->fun_existence_lock); function->fun_flags |= FUN_obsolete; } catch (const Firebird::Exception&) {} // no-op return 0; } void Function::remove(thread_db* tdbb) { if (fun_existence_lock) { LCK_release(tdbb, fun_existence_lock); delete fun_existence_lock; fun_existence_lock = NULL; } if (!(fun_flags & FUN_being_altered) && !fun_use_count) { delete this; } else { setName(QualifiedName()); setSecurityName(""); setId(0); fun_defaults = 0; } } void Function::parseBlr(thread_db* tdbb, bid* blob_id, CompilerScratch* csb) { fb_assert(blob_id && !blob_id->isEmpty()); jrd_req* request = getRequest(); MET_parse_blob(tdbb, NULL, blob_id, &csb, &request, false); setRequest(request); } void Function::addRef() { fun_use_count++; } void Function::release(thread_db* tdbb) { if (fun_use_count) { fun_use_count--; Database* const dbb = tdbb->getDatabase(); if (fun_use_count == 0 && dbb->dbb_functions[getId()] != this) { if (getRequest()) { CMP_release(tdbb, getRequest()); setRequest(NULL); } fun_flags &= ~FUN_being_altered; remove(tdbb); } } } void Function::releaseLocks(thread_db* tdbb) { if (fun_existence_lock) { LCK_release(tdbb, fun_existence_lock); fun_flags |= FUN_check_existence; fun_use_count = 0; } } dsc* Function::execute(thread_db* tdbb, jrd_nod* args, impure_value* value) const { if (isUndefined()) { status_exception::raise( Arg::Gds(isc_func_pack_not_implemented) << Arg::Str(getName().identifier) << Arg::Str(getName().package)); } const fun_repeat* const return_ptr = &fun_args[fun_return_arg]; value->vlu_desc = return_ptr->fun_parameter->prm_desc; // If the return data type is any of the string types, // allocate space to hold value if (value->vlu_desc.dsc_dtype <= dtype_varying) { const USHORT ret_length = value->vlu_desc.dsc_length; VaryingString* string = value->vlu_string; if (string && string->str_length < ret_length) { delete string; string = NULL; } if (!string) { string = FB_NEW_RPT(*tdbb->getDefaultPool(), ret_length) VaryingString; string->str_length = ret_length; value->vlu_string = string; } value->vlu_desc.dsc_address = string->str_data; } else { value->vlu_desc.dsc_address = (UCHAR*) &value->vlu_misc; } if (fun_entrypoint) { FUN_evaluate(tdbb, this, args, value); } else if (fun_external) { fun_external->execute(tdbb, args, value); } else { fb_assert(getRequest()); fb_assert(!fun_return_arg); Database* const dbb = tdbb->getDatabase(); jrd_req* const request = tdbb->getRequest(); ULONG in_msg_length = 0; Firebird::UCharBuffer in_buffer; for (unsigned i = 1; i < fun_args.getCount(); i++) { Parameter* const parameter = fun_args[i].fun_parameter; dsc arg_desc = parameter->prm_desc; if (arg_desc.dsc_dtype >= dtype_aligned) { in_msg_length = FB_ALIGN(in_msg_length, type_alignments[arg_desc.dsc_dtype]); } const USHORT arg_offset = in_msg_length; in_msg_length += arg_desc.dsc_length; in_msg_length = FB_ALIGN(in_msg_length, sizeof(SSHORT)); const USHORT null_offset = in_msg_length; in_msg_length += sizeof(SSHORT); fb_assert(in_msg_length < ULONG(MAX_USHORT)); in_buffer.grow(in_msg_length); jrd_nod* const node = (i - 1 < args->nod_count) ? args->nod_arg[i - 1] : parameter->prm_default_value; dsc* const src_desc = EVL_expr(tdbb, node); if (src_desc && !(request->req_flags & req_null)) { arg_desc.dsc_address = in_buffer.begin() + arg_offset; MOV_move(tdbb, src_desc, &arg_desc); } else { UCHAR* const null_ptr = in_buffer.begin() + null_offset; *reinterpret_cast(null_ptr) = TRUE; } } UCHAR* const in_msg = in_msg_length ? in_buffer.begin() : NULL; dsc ret_desc = fun_args[fun_return_arg].fun_parameter->prm_desc; USHORT out_msg_length = ret_desc.dsc_length; out_msg_length = FB_ALIGN(out_msg_length, sizeof(SSHORT)); const USHORT null_offset = out_msg_length; out_msg_length += sizeof(SSHORT); Firebird::UCharBuffer out_buffer; UCHAR* const out_msg = out_buffer.getBuffer(out_msg_length); ret_desc.dsc_address = out_msg; jrd_req* const new_request = EXE_find_request(tdbb, getRequest(), false); try { // Save the old pool Jrd::ContextPoolHolder context(tdbb, request->req_pool); jrd_tra* const transaction = request->req_transaction; const SLONG save_point_number = transaction->tra_save_point ? transaction->tra_save_point->sav_number : 0; new_request->req_timestamp = request->req_timestamp; EXE_start(tdbb, new_request, transaction); if (in_msg) { EXE_send(tdbb, new_request, 0, in_msg_length, in_msg); } EXE_receive(tdbb, new_request, 1, out_msg_length, out_msg); // Clean up all savepoints started during execution of the function if (transaction != dbb->dbb_sys_trans) { for (const Savepoint* save_point = transaction->tra_save_point; save_point && save_point_number < save_point->sav_number; save_point = transaction->tra_save_point) { VIO_verb_cleanup(tdbb, transaction); } } } catch (const Firebird::Exception&) { tdbb->setRequest(request); EXE_unwind(tdbb, new_request); new_request->req_attachment = NULL; new_request->req_flags &= ~req_in_use; throw; } tdbb->setRequest(request); EXE_unwind(tdbb, new_request); new_request->req_attachment = NULL; new_request->req_flags &= ~req_in_use; UCHAR* const null_ptr = out_msg + null_offset; if (*reinterpret_cast(null_ptr)) { request->req_flags |= req_null; } else { request->req_flags &= ~req_null; MOV_move(tdbb, &ret_desc, &value->vlu_desc); } } return &value->vlu_desc; } USHORT Function::incrementAlterCount() { if (fun_alter_count == Function::MAX_ALTER_COUNT) { status_exception::raise(Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_udf_name) << Arg::Str(getName().toString()) << Arg::Gds(isc_version_err)); } return ++fun_alter_count; }