mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-27 06:43:04 +01:00
781 lines
21 KiB
C++
781 lines
21 KiB
C++
/*
|
|
* 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/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 "../dsql/StmtNodes.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"
|
|
#include "../jrd/Collation.h"
|
|
|
|
using namespace Firebird;
|
|
using namespace Jrd;
|
|
|
|
|
|
template <typename T> static void makeSubRoutines(thread_db* tdbb, JrdStatement* statement,
|
|
CompilerScratch* csb, T& subs);
|
|
|
|
|
|
// 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),
|
|
parentStatement(NULL),
|
|
subStatements(*p),
|
|
fors(*p),
|
|
invariants(*p),
|
|
blr(*p),
|
|
mapFieldInfo(*p),
|
|
mapItemInfo(*p)
|
|
{
|
|
try
|
|
{
|
|
makeSubRoutines(tdbb, this, csb, csb->subProcedures);
|
|
makeSubRoutines(tdbb, this, csb, csb->subFunctions);
|
|
|
|
topNode = (csb->csb_node->kind == DmlNode::KIND_STATEMENT) ?
|
|
static_cast<StmtNode*>(csb->csb_node) : NULL;
|
|
|
|
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;
|
|
blrVersion = csb->blrVersion;
|
|
|
|
// 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:
|
|
case Resource::rsc_function:
|
|
{
|
|
Routine* routine = resource->rsc_routine;
|
|
routine->addRef();
|
|
|
|
#ifdef DEBUG_PROCS
|
|
string buffer;
|
|
buffer.printf(
|
|
"Called from JrdStatement::makeRequest:\n\t Incrementing use count of %s\n",
|
|
routine->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;
|
|
}
|
|
|
|
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 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)
|
|
{
|
|
// stream is known to be materialized after retrieving thus making its data outdated,
|
|
// let's indicate this fact to force a re-fetch when necessary
|
|
if (tail->csb_flags & csb_offline)
|
|
rpb->rpb_stream_flags |= RPB_s_offline;
|
|
|
|
// fetch input stream for update if all booleans matched against indices
|
|
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;
|
|
}
|
|
}
|
|
catch (Exception&)
|
|
{
|
|
for (JrdStatement** subStatement = subStatements.begin();
|
|
subStatement != subStatements.end();
|
|
++subStatement)
|
|
{
|
|
(*subStatement)->release(tdbb);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
|
|
DmlNode::doPass1(tdbb, csb, &csb->csb_node);
|
|
|
|
// CVC: I'm going to allocate the map before the loop to avoid alloc/dealloc calls.
|
|
AutoPtr<StreamType, ArrayDelete<StreamType> > localMap(FB_NEW(*tdbb->getDefaultPool())
|
|
StreamType[STREAM_MAP_LENGTH]);
|
|
|
|
// 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;
|
|
//StreamType 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, localMap);
|
|
|
|
csb->csb_remap_variable = (csb->csb_variables ? csb->csb_variables->count() : 0) + 1;
|
|
|
|
if (fieldInfo.validationExpr)
|
|
{
|
|
NodeCopier copier(csb, localMap);
|
|
fieldInfo.validationExpr = copier.copy(tdbb, fieldInfo.validationExpr);
|
|
}
|
|
|
|
DmlNode::doPass1(tdbb, csb, fieldInfo.defaultValue.getAddress());
|
|
DmlNode::doPass1(tdbb, csb, fieldInfo.validationExpr.getAddress());
|
|
}
|
|
|
|
if (csb->csb_node->kind == DmlNode::KIND_STATEMENT)
|
|
StmtNode::doPass2(tdbb, csb, reinterpret_cast<StmtNode**>(&csb->csb_node), NULL);
|
|
else
|
|
ExprNode::doPass2(tdbb, csb, &csb->csb_node);
|
|
|
|
// Compile (pass2) domains DEFAULT and constraints
|
|
for (bool found = accessor.getFirst(); found; found = accessor.getNext())
|
|
{
|
|
FieldInfo& fieldInfo = accessor.current()->second;
|
|
ExprNode::doPass2(tdbb, csb, fieldInfo.defaultValue.getAddress());
|
|
ExprNode::doPass2(tdbb, csb, fieldInfo.validationExpr.getAddress());
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (statement)
|
|
{
|
|
// Release sub statements.
|
|
for (JrdStatement** subStatement = statement->subStatements.begin();
|
|
subStatement != statement->subStatements.end();
|
|
++subStatement)
|
|
{
|
|
(*subStatement)->release(tdbb);
|
|
}
|
|
}
|
|
|
|
ex.stuff_exception(tdbb->tdbb_status_vector);
|
|
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;
|
|
|
|
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);
|
|
Attachment* const attachment = tdbb->getAttachment();
|
|
|
|
if (!this)
|
|
BUGCHECK(167); /* msg 167 invalid SEND request */
|
|
|
|
// 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* const request = FB_NEW(*pool) jrd_req(attachment, this, parentStats);
|
|
request->req_id = dbb->generateStatementId(tdbb);
|
|
|
|
requests[level] = request;
|
|
|
|
return request;
|
|
}
|
|
|
|
// Check that we have enough rights to access all resources this request touches including
|
|
// resources it used indirectly 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;
|
|
}
|
|
|
|
fb_assert(routine);
|
|
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,
|
|
true, 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,
|
|
true, 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, true, access->acc_name, access->acc_r_name);
|
|
}
|
|
}
|
|
|
|
// Release a statement.
|
|
void JrdStatement::release(thread_db* tdbb)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
// Release sub statements.
|
|
for (JrdStatement** subStatement = subStatements.begin();
|
|
subStatement != subStatements.end();
|
|
++subStatement)
|
|
{
|
|
(*subStatement)->release(tdbb);
|
|
}
|
|
|
|
// 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:
|
|
case Resource::rsc_function:
|
|
resource->rsc_routine->release(tdbb);
|
|
break;
|
|
|
|
case Resource::rsc_collation:
|
|
{
|
|
Collation* coll = resource->rsc_coll;
|
|
coll->decUseCount(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;
|
|
|
|
// Sub statement pool is the same of the main statement, so don't delete it.
|
|
if (!parentStatement)
|
|
{
|
|
Jrd::Attachment* const att = tdbb->getAttachment();
|
|
att->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 (FB_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 &&
|
|
(ownerRelation->rel_name == access->acc_r_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, true, 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 (FB_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)
|
|
{
|
|
FB_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Make sub routines.
|
|
template <typename T> static void makeSubRoutines(thread_db* tdbb, JrdStatement* statement,
|
|
CompilerScratch* csb, T& subs)
|
|
{
|
|
typename T::Accessor subAccessor(&subs);
|
|
|
|
for (bool found = subAccessor.getFirst(); found; found = subAccessor.getNext())
|
|
{
|
|
typename T::ValueType subNode = subAccessor.current()->second;
|
|
Routine* subRoutine = subNode->routine;
|
|
CompilerScratch*& subCsb = subNode->subCsb;
|
|
|
|
JrdStatement* subStatement = JrdStatement::makeStatement(tdbb, subCsb, true);
|
|
subStatement->parentStatement = statement;
|
|
subRoutine->setStatement(subStatement);
|
|
|
|
switch (subRoutine->getObjectType())
|
|
{
|
|
case obj_procedure:
|
|
subStatement->procedure = static_cast<jrd_prc*>(subRoutine);
|
|
break;
|
|
|
|
case obj_udf:
|
|
subStatement->function = static_cast<Function*>(subRoutine);
|
|
break;
|
|
|
|
default:
|
|
fb_assert(false);
|
|
break;
|
|
}
|
|
|
|
// Move dependencies and permissions from the sub routine to the parent.
|
|
|
|
for (CompilerScratch::Dependency* dependency = subCsb->csb_dependencies.begin();
|
|
dependency != subCsb->csb_dependencies.end();
|
|
++dependency)
|
|
{
|
|
csb->csb_dependencies.push(*dependency);
|
|
}
|
|
|
|
for (ExternalAccess* access = subCsb->csb_external.begin();
|
|
access != subCsb->csb_external.end();
|
|
++access)
|
|
{
|
|
FB_SIZE_T i;
|
|
if (!csb->csb_external.find(*access, i))
|
|
csb->csb_external.insert(i, *access);
|
|
}
|
|
|
|
for (AccessItem* access = subCsb->csb_access.begin();
|
|
access != subCsb->csb_access.end();
|
|
++access)
|
|
{
|
|
FB_SIZE_T i;
|
|
if (!csb->csb_access.find(*access, i))
|
|
csb->csb_access.insert(i, *access);
|
|
}
|
|
|
|
delete subCsb;
|
|
subCsb = NULL;
|
|
|
|
statement->subStatements.add(subStatement);
|
|
}
|
|
}
|