8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-31 10:43:02 +01:00
firebird-mirror/src/jrd/JrdStatement.cpp

668 lines
18 KiB
C++
Raw Normal View History

/*
* 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): ______________________________________.
* Adriano dos Santos Fernandes
*/
#include "firebird.h"
#include "../jrd/common.h"
#include "../jrd/JrdStatement.h"
#include "../jrd/Attachment.h"
#include "../jrd/intl_classes.h"
#include "../jrd/acl.h"
#include "../jrd/req.h"
#include "../jrd/tra.h"
#include "../jrd/val.h"
#include "../jrd/align.h"
#include "../dsql/Nodes.h"
#include "../jrd/Function.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/scl_proto.h"
using namespace Firebird;
using namespace Jrd;
// Start to turn a parsed scratch into a statement. This is completed by makeStatement.
JrdStatement::JrdStatement(thread_db* tdbb, MemoryPool* p, CompilerScratch* csb)
: pool(p),
rpbsSetup(*p),
requests(*p),
externalList(*p),
accessList(*p),
resources(*p),
triggerName(*p),
fors(*p),
execStmts(*p),
invariants(*p),
blr(*p),
mapFieldInfo(*p),
mapItemInfo(*p)
{
topNode = csb->csb_node;
accessList = csb->csb_access;
externalList = csb->csb_external;
mapFieldInfo.takeOwnership(csb->csb_map_field_info);
resources = csb->csb_resources; // Assign array contents
impureSize = csb->csb_impure;
if (csb->csb_g_flags & csb_blr_version4)
flags |= FLAG_VERSION4;
// Take out existence locks on resources used in statement. This is
// a little complicated since relation locks MUST be taken before
// index locks.
for (Resource* resource = resources.begin(); resource != resources.end(); ++resource)
{
switch (resource->rsc_type)
{
case Resource::rsc_relation:
{
jrd_rel* relation = resource->rsc_rel;
MET_post_existence(tdbb, relation);
break;
}
case Resource::rsc_index:
{
jrd_rel* relation = resource->rsc_rel;
IndexLock* index = CMP_get_index_lock(tdbb, relation, resource->rsc_id);
if (index)
{
++index->idl_count;
if (index->idl_count == 1) {
LCK_lock(tdbb, index->idl_lock, LCK_SR, LCK_WAIT);
}
}
break;
}
case Resource::rsc_procedure:
{
jrd_prc* procedure = resource->rsc_prc;
procedure->prc_use_count++;
#ifdef DEBUG_PROCS
{
string buffer;
buffer.printf(
"Called from JrdStatement::makeRequest:\n\t Incrementing use count of %s\n",
procedure->getName()->toString().c_str());
JRD_print_procedure_info(tdbb, buffer.c_str());
}
#endif
break;
}
case Resource::rsc_collation:
{
Collation* coll = resource->rsc_coll;
coll->incUseCount(tdbb);
break;
}
case Resource::rsc_function:
resource->rsc_fun->addRef();
break;
default:
BUGCHECK(219); // msg 219 request of unknown resource
}
}
// make a vector of all used RSEs
fors = csb->csb_fors;
// make a vector of all used ExecuteStatements into
2010-08-12 02:59:48 +02:00
for (Array<jrd_nod*>::iterator i = csb->csb_exec_sta.begin(); i != csb->csb_exec_sta.end(); ++i)
execStmts.add(*i);
// make a vector of all invariant-type nodes, so that we will
// be able to easily reinitialize them when we restart the request
invariants.join(csb->csb_invariants);
rpbsSetup.grow(csb->csb_n_stream);
CompilerScratch::csb_repeat* tail = csb->csb_rpt.begin();
const CompilerScratch::csb_repeat* const streams_end = tail + csb->csb_n_stream;
for (record_param* rpb = rpbsSetup.begin(); tail < streams_end; ++rpb, ++tail)
{
// fetch input stream for update if all booleans matched against indices
2010-04-29 07:13:03 +02:00
if ((tail->csb_flags & csb_update) && !(tail->csb_flags & csb_unmatched))
rpb->rpb_stream_flags |= RPB_s_update;
// if no fields are referenced and this stream is not intended for update,
// mark the stream as not requiring record's data
if (!tail->csb_fields && !(tail->csb_flags & csb_update))
rpb->rpb_stream_flags |= RPB_s_no_data;
rpb->rpb_relation = tail->csb_relation;
delete tail->csb_fields;
tail->csb_fields = NULL;
}
}
// Turn a parsed scratch into a statement.
JrdStatement* JrdStatement::makeStatement(thread_db* tdbb, CompilerScratch* csb, bool internalFlag)
{
DEV_BLKCHK(csb, type_csb);
SET_TDBB(tdbb);
Database* const dbb = tdbb->getDatabase();
fb_assert(dbb);
jrd_req* const old_request = tdbb->getRequest();
tdbb->setRequest(NULL);
JrdStatement* statement = NULL;
try
{
// Once any expansion required has been done, make a pass to assign offsets
// into the impure area and throw away any unnecessary crude. Execution
// optimizations can be performed here.
csb->csb_node = CMP_pass1(tdbb, csb, csb->csb_node);
// Copy and compile (pass1) domains DEFAULT and constraints.
MapFieldInfo::Accessor accessor(&csb->csb_map_field_info);
for (bool found = accessor.getFirst(); found; found = accessor.getNext())
{
FieldInfo& fieldInfo = accessor.current()->second;
UCHAR local_map[MAP_LENGTH];
AutoSetRestore<USHORT> autoRemapVariable(&csb->csb_remap_variable,
(csb->csb_variables ? csb->csb_variables->count() : 0) + 1);
fieldInfo.defaultValue = NodeCopier::copy(tdbb, csb, fieldInfo.defaultValue, local_map);
csb->csb_remap_variable = (csb->csb_variables ? csb->csb_variables->count() : 0) + 1;
fieldInfo.validation = NodeCopier::copy(tdbb, csb, fieldInfo.validation, local_map);
fieldInfo.defaultValue = CMP_pass1(tdbb, csb, fieldInfo.defaultValue);
fieldInfo.validation = CMP_pass1(tdbb, csb, fieldInfo.validation);
}
csb->csb_exec_sta.clear();
csb->csb_node = CMP_pass2(tdbb, csb, csb->csb_node, 0);
// Compile (pass2) domains DEFAULT and constraints
for (bool found = accessor.getFirst(); found; found = accessor.getNext())
{
FieldInfo& fieldInfo = accessor.current()->second;
fieldInfo.defaultValue = CMP_pass2(tdbb, csb, fieldInfo.defaultValue, 0);
fieldInfo.validation = CMP_pass2(tdbb, csb, fieldInfo.validation, 0);
}
if (csb->csb_impure > MAX_REQUEST_SIZE)
IBERROR(226); // msg 226 request size limit exceeded
// Build the statement and the final request block.
MemoryPool* const pool = tdbb->getDefaultPool();
statement = FB_NEW(*pool) JrdStatement(tdbb, pool, csb);
tdbb->setRequest(old_request);
} // try
catch (const Exception& ex)
{
stuff_exception(tdbb->tdbb_status_vector, ex);
tdbb->setRequest(old_request);
ERR_punt();
}
if (internalFlag)
statement->flags |= FLAG_INTERNAL;
else
tdbb->bumpStats(RuntimeStatistics::STMT_PREPARES);
return statement;
}
// Turn a parsed scratch into an executable request.
jrd_req* JrdStatement::makeRequest(thread_db* tdbb, CompilerScratch* csb, bool internalFlag)
{
JrdStatement* statement = makeStatement(tdbb, csb, internalFlag);
return statement->getRequest(tdbb, 0);
}
// Returns function or procedure routine.
const Routine* JrdStatement::getRoutine() const
{
fb_assert(!(procedure && function));
if (procedure)
return procedure;
2010-04-29 07:13:03 +02:00
return function;
}
// Determine if any request of this statement are active.
bool JrdStatement::isActive() const
{
for (const jrd_req* const* request = requests.begin(); request != requests.end(); ++request)
{
if (*request && ((*request)->req_flags & req_in_use))
return true;
}
return false;
}
jrd_req* JrdStatement::findRequest(thread_db* tdbb)
{
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
Attachment* const attachment = tdbb->getAttachment();
if (!this)
BUGCHECK(167); /* msg 167 invalid SEND request */
Database::CheckoutLockGuard guard(dbb, dbb->dbb_exe_clone_mutex);
// Search clones for one request in use by this attachment.
// If not found, return first inactive request.
jrd_req* clone = NULL;
USHORT count = 0;
const USHORT clones = requests.getCount();
USHORT n;
for (n = 0; n < clones; ++n)
{
jrd_req* next = getRequest(tdbb, n);
if (next->req_attachment == attachment)
{
if (!(next->req_flags & req_in_use))
{
clone = next;
break;
}
++count;
}
else if (!(next->req_flags & req_in_use) && !clone)
clone = next;
}
if (count > MAX_CLONES)
ERR_post(Arg::Gds(isc_req_max_clones_exceeded));
if (!clone)
clone = getRequest(tdbb, n);
clone->setAttachment(attachment);
clone->req_stats.reset();
clone->req_base_stats.reset();
clone->req_flags |= req_in_use;
return clone;
}
jrd_req* JrdStatement::getRequest(thread_db* tdbb, USHORT level)
{
SET_TDBB(tdbb);
Jrd::Attachment* const attachment = tdbb->getAttachment();
Database* const dbb = tdbb->getDatabase();
fb_assert(dbb);
if (level < requests.getCount() && requests[level])
return requests[level];
requests.grow(level + 1);
MemoryStats* const parentStats = (flags & FLAG_INTERNAL) ?
&dbb->dbb_memory_stats : &attachment->att_memory_stats;
// Create the request.
jrd_req* request = FB_NEW(*pool) jrd_req(attachment, this, parentStats);
request->req_id = fb_utils::genUniqueId();
requests[level] = request;
return request;
}
// Check that we have enough rights to access all resources this request touches including
// resources it used indirectecty via procedures or triggers.
void JrdStatement::verifyAccess(thread_db* tdbb)
{
SET_TDBB(tdbb);
ExternalAccessList external;
buildExternalAccess(tdbb, external);
for (ExternalAccess* item = external.begin(); item != external.end(); ++item)
{
const Routine* routine = NULL;
int aclType;
if (item->exa_action == ExternalAccess::exa_procedure)
{
routine = MET_lookup_procedure_id(tdbb, item->exa_prc_id, false, false, 0);
aclType = id_procedure;
}
else if (item->exa_action == ExternalAccess::exa_function)
{
routine = Function::lookup(tdbb, item->exa_fun_id, false, false, 0);
aclType = id_function;
}
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:
verifyTriggerAccess(tdbb, relation, relation->rel_pre_store, view);
verifyTriggerAccess(tdbb, relation, relation->rel_post_store, view);
break;
case ExternalAccess::exa_update:
verifyTriggerAccess(tdbb, relation, relation->rel_pre_modify, view);
verifyTriggerAccess(tdbb, relation, relation->rel_post_modify, view);
break;
case ExternalAccess::exa_delete:
verifyTriggerAccess(tdbb, relation, relation->rel_pre_erase, view);
verifyTriggerAccess(tdbb, relation, relation->rel_post_erase, view);
break;
default:
fb_assert(false);
}
continue;
}
if (!routine->getStatement())
continue;
for (const AccessItem* access = routine->getStatement()->accessList.begin();
access != routine->getStatement()->accessList.end();
++access)
{
const SecurityClass* sec_class = SCL_get_class(tdbb, access->acc_security_name.c_str());
if (routine->getName().package.isEmpty())
{
SCL_check_access(tdbb, sec_class, access->acc_view_id, aclType,
routine->getName().identifier, access->acc_mask, access->acc_type,
access->acc_name, access->acc_r_name);
}
else
{
SCL_check_access(tdbb, sec_class, access->acc_view_id,
id_package, routine->getName().package,
access->acc_mask, access->acc_type,
access->acc_name, access->acc_r_name);
}
}
}
// 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.
// In v3.0, this rule also works for external procedures and triggers.
jrd_tra* transaction = tdbb->getTransaction();
const bool useCallerPrivs = transaction && transaction->tra_callback_count;
for (const AccessItem* access = accessList.begin(); access != accessList.end(); ++access)
{
const SecurityClass* sec_class = SCL_get_class(tdbb, access->acc_security_name.c_str());
MetaName objName;
SLONG objType = 0;
if (useCallerPrivs)
{
switch (transaction->tra_caller_name.type)
{
case obj_trigger:
objType = id_trigger;
break;
case obj_procedure:
objType = id_procedure;
break;
case obj_udf:
objType = id_function;
break;
case obj_package_header:
objType = id_package;
break;
case obj_type_MAX: // CallerName() constructor
fb_assert(transaction->tra_caller_name.name.isEmpty());
break;
default:
fb_assert(false);
}
objName = transaction->tra_caller_name.name;
}
SCL_check_access(tdbb, sec_class, access->acc_view_id, objType, objName,
access->acc_mask, access->acc_type, access->acc_name, access->acc_r_name);
}
}
// Release a statement.
void JrdStatement::release(thread_db* tdbb)
{
SET_TDBB(tdbb);
Database* dbb = tdbb->getDatabase();
// release existence locks on references
for (Resource* resource = resources.begin(); resource != resources.end(); ++resource)
{
switch (resource->rsc_type)
{
case Resource::rsc_relation:
{
jrd_rel* relation = resource->rsc_rel;
MET_release_existence(tdbb, relation);
break;
}
case Resource::rsc_index:
{
jrd_rel* relation = resource->rsc_rel;
IndexLock* index = CMP_get_index_lock(tdbb, relation, resource->rsc_id);
if (index && index->idl_count)
{
--index->idl_count;
if (!index->idl_count)
LCK_release(tdbb, index->idl_lock);
}
break;
}
case Resource::rsc_procedure:
CMP_decrement_prc_use_count(tdbb, resource->rsc_prc);
break;
case Resource::rsc_collation:
{
Collation* coll = resource->rsc_coll;
coll->decUseCount(tdbb);
break;
}
case Resource::rsc_function:
resource->rsc_fun->release(tdbb);
break;
default:
BUGCHECK(220); // msg 220 release of unknown resource
break;
}
}
for (jrd_req** instance = requests.begin(); instance != requests.end(); ++instance)
EXE_release(tdbb, *instance);
sqlText = NULL;
dbb->deletePool(pool);
}
// Check that we have enough rights to access all resources this list of triggers touches.
void JrdStatement::verifyTriggerAccess(thread_db* tdbb, jrd_rel* ownerRelation,
trig_vec* triggers, jrd_rel* view)
{
if (!triggers)
return;
SET_TDBB(tdbb);
for (size_t i = 0; i < triggers->getCount(); i++)
{
Trigger& t = (*triggers)[i];
t.compile(tdbb);
if (!t.statement)
continue;
for (const AccessItem* access = t.statement->accessList.begin();
access != t.statement->accessList.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 (!(ownerRelation->rel_flags & REL_system))
{
if (access->acc_type == SCL_object_table &&
(ownerRelation->rel_name == access->acc_name))
{
continue;
}
if (access->acc_type == SCL_object_column &&
(MET_lookup_field(tdbb, ownerRelation, access->acc_name,
&access->acc_security_name) >= 0 ||
MET_relation_default_class(tdbb, ownerRelation->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),
id_trigger, t.statement->triggerName, access->acc_mask,
access->acc_type, access->acc_name, access->acc_r_name);
}
}
}
// Invoke buildExternalAccess for triggers in vector
inline void JrdStatement::triggersExternalAccess(thread_db* tdbb, ExternalAccessList& list,
trig_vec* tvec)
{
if (!tvec)
return;
for (size_t i = 0; i < tvec->getCount(); i++)
{
Trigger& t = (*tvec)[i];
t.compile(tdbb);
if (t.statement)
t.statement->buildExternalAccess(tdbb, list);
}
}
// Recursively walk external dependencies (procedures, triggers) for request to assemble full
// list of requests it depends on.
void JrdStatement::buildExternalAccess(thread_db* tdbb, ExternalAccessList& list)
{
for (ExternalAccess* item = externalList.begin(); item != externalList.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* const procedure = MET_lookup_procedure_id(tdbb, item->exa_prc_id, false, false, 0);
if (procedure && procedure->getStatement())
procedure->getStatement()->buildExternalAccess(tdbb, list);
}
else if (item->exa_action == ExternalAccess::exa_function)
{
Function* const function = Function::lookup(tdbb, item->exa_fun_id, false, false, 0);
if (function && function->getStatement())
function->getStatement()->buildExternalAccess(tdbb, list);
}
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
}
triggersExternalAccess(tdbb, list, vec1);
triggersExternalAccess(tdbb, list, vec2);
}
}
}