mirror of
https://github.com/FirebirdSQL/firebird.git
synced 2025-01-31 06:43:02 +01:00
a3064848d6
Store ValueExprNodes instead of jrd_nod in the metadata cache. Make RecordSourceNode child of ExprNode as they share most operations. Get rid of the JRD visitors in favor of direct calls. Convert assignments statement lists created inside expressions to separate source and targets ValuesExprNodes.
2364 lines
63 KiB
C++
2364 lines
63 KiB
C++
/*
|
|
* 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 <string.h>
|
|
#include <stdlib.h> // abort
|
|
#include "../common/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/sort.h"
|
|
#include "../common/gdsassert.h"
|
|
#include "../jrd/cmp_proto.h"
|
|
#include "../common/dsc_proto.h"
|
|
#include "../jrd/err_proto.h"
|
|
#include "../jrd/exe_proto.h"
|
|
#include "../jrd/fun_proto.h"
|
|
#include "../yvalve/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/met_proto.h"
|
|
#include "../jrd/mov_proto.h"
|
|
#include "../common/dsc_proto.h"
|
|
#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"
|
|
#include "../jrd/RecordSourceNodes.h"
|
|
#include "../jrd/ValuesImpl.h"
|
|
#include "../jrd/recsrc/RecordSource.h"
|
|
#include "../jrd/recsrc/Cursor.h"
|
|
#include "../jrd/Function.h"
|
|
#include "../dsql/BoolNodes.h"
|
|
#include "../dsql/ExprNodes.h"
|
|
#include "../dsql/StmtNodes.h"
|
|
|
|
using namespace Jrd;
|
|
using namespace Firebird;
|
|
|
|
|
|
namespace
|
|
{
|
|
// Node copier for views.
|
|
class ViewNodeCopier : public NodeCopier
|
|
{
|
|
public:
|
|
ViewNodeCopier(CompilerScratch* aCsb, UCHAR* aRemap)
|
|
: NodeCopier(aCsb, aRemap)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
virtual bool remapArgument()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
virtual USHORT remapField(USHORT stream, USHORT fldId)
|
|
{
|
|
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
|
|
jrd_fld* field = MET_get_field(relation, fldId);
|
|
|
|
if (field->fld_source)
|
|
fldId = field->fld_source->as<FieldNode>()->fieldId;
|
|
|
|
return fldId;
|
|
}
|
|
};
|
|
|
|
// Node copier that remaps the field id 0 of stream 0 to a given field id.
|
|
class RemapFieldNodeCopier : public NodeCopier
|
|
{
|
|
public:
|
|
RemapFieldNodeCopier(CompilerScratch* aCsb, UCHAR* aRemap, USHORT aFldId)
|
|
: NodeCopier(aCsb, aRemap),
|
|
fldId(aFldId)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
virtual USHORT getFieldId(FieldNode* field)
|
|
{
|
|
if (field->byId && field->fieldId == 0 && field->fieldStream == 0)
|
|
return fldId;
|
|
|
|
return NodeCopier::getFieldId(field);
|
|
}
|
|
|
|
private:
|
|
USHORT fldId;
|
|
};
|
|
} // namespace
|
|
|
|
static jrd_nod* make_defaults(thread_db*, CompilerScratch*, USHORT, jrd_nod*);
|
|
static jrd_nod* make_validation(thread_db*, CompilerScratch*, USHORT);
|
|
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 bool pass1_store(thread_db*, CompilerScratch*, jrd_nod*);
|
|
static RelationSourceNode* pass1_update(thread_db*, CompilerScratch*, jrd_rel*, const trig_vec*, USHORT, USHORT,
|
|
SecurityClass::flags_t, jrd_rel*, USHORT);
|
|
static void post_trigger_access(CompilerScratch*, jrd_rel*, ExternalAccess::exa_act, jrd_rel*);
|
|
|
|
#ifdef CMP_DEBUG
|
|
#include <stdarg.h>
|
|
IMPLEMENT_TRACE_ROUTINE(cmp_trace, "CMP")
|
|
#endif
|
|
|
|
|
|
// Clone a node.
|
|
ValueExprNode* CMP_clone_node(thread_db* tdbb, CompilerScratch* csb, ValueExprNode* node)
|
|
{
|
|
return NodeCopier::copy(tdbb, csb, node, NULL);
|
|
}
|
|
|
|
|
|
// Clone a value node for the optimizer.
|
|
// Make a copy of the node (if necessary) and assign impure space.
|
|
|
|
ValueExprNode* CMP_clone_node_opt(thread_db* tdbb, CompilerScratch* csb, ValueExprNode* node)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
if (node->is<ParameterNode>())
|
|
return node;
|
|
|
|
ValueExprNode* clone = NodeCopier::copy(tdbb, csb, node, NULL);
|
|
CMP_pass2(tdbb, csb, clone);
|
|
|
|
return clone;
|
|
}
|
|
|
|
BoolExprNode* CMP_clone_node_opt(thread_db* tdbb, CompilerScratch* csb, BoolExprNode* node)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
NodeCopier copier(csb, NULL);
|
|
BoolExprNode* clone = copier.copy(tdbb, node);
|
|
|
|
CMP_pass2(tdbb, csb, clone);
|
|
|
|
return clone;
|
|
}
|
|
|
|
|
|
jrd_req* CMP_compile2(thread_db* tdbb, const UCHAR* blr, ULONG blr_length, bool 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, blr_length, internal_flag, dbginfo_length, dbginfo);
|
|
|
|
request = JrdStatement::makeRequest(tdbb, csb, internal_flag);
|
|
new_pool->setStatsGroup(request->req_memory_stats);
|
|
|
|
#ifdef CMP_DEBUG
|
|
if (csb->csb_dump.hasData())
|
|
{
|
|
csb->dump("streams:\n");
|
|
for (unsigned i = 0; i < csb->csb_n_stream; ++i)
|
|
{
|
|
const CompilerScratch::csb_repeat& s = csb->csb_rpt[i];
|
|
csb->dump(
|
|
"\t%2d - view_stream: %2d; alias: %s; relation: %s; procedure: %s; view: %s\n",
|
|
i, s.csb_view_stream,
|
|
(s.csb_alias ? s.csb_alias->c_str() : ""),
|
|
(s.csb_relation ? s.csb_relation->rel_name.c_str() : ""),
|
|
(s.csb_procedure ? s.csb_procedure->getName().toString().c_str() : ""),
|
|
(s.csb_view ? s.csb_view->rel_name.c_str() : ""));
|
|
}
|
|
|
|
cmp_trace("\n%s\n", csb->csb_dump.c_str());
|
|
}
|
|
#endif
|
|
|
|
request->getStatement()->verifyAccess(tdbb);
|
|
|
|
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];
|
|
}
|
|
|
|
|
|
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);
|
|
Database* const dbb = tdbb->getDatabase();
|
|
|
|
CMP_shutdown_database(tdbb); // Shutdown shared database locks.
|
|
|
|
// And release the system requests.
|
|
|
|
for (JrdStatement** itr = dbb->dbb_internal.begin(); itr != dbb->dbb_internal.end(); ++itr)
|
|
{
|
|
if (*itr)
|
|
(*itr)->release(tdbb);
|
|
}
|
|
|
|
for (JrdStatement** itr = dbb->dbb_dyn_req.begin(); itr != dbb->dbb_dyn_req.end(); ++itr)
|
|
{
|
|
if (*itr)
|
|
(*itr)->release(tdbb);
|
|
}
|
|
}
|
|
|
|
|
|
const 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.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
switch (node->nod_type)
|
|
{
|
|
case nod_class_exprnode_jrd:
|
|
{
|
|
ValueExprNode* exprNode = reinterpret_cast<ValueExprNode*>(node->nod_arg[0]);
|
|
exprNode->getDesc(tdbb, csb, desc);
|
|
return;
|
|
}
|
|
|
|
default:
|
|
fb_assert(false);
|
|
ERR_post(Arg::Gds(isc_datype_notsup)); // data type not supported for arithmetic
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
IndexLock* CMP_get_index_lock(thread_db* tdbb, jrd_rel* relation, USHORT id)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ g e t _ i n d e x _ l o c k
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Get index lock block for index. If one doesn't exist,
|
|
* make one.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
|
|
DEV_BLKCHK(relation, type_rel);
|
|
|
|
if (relation->rel_id < (USHORT) rel_MAX) {
|
|
return NULL;
|
|
}
|
|
|
|
// for to find an existing block
|
|
|
|
for (IndexLock* index = relation->rel_index_locks; index; index = index->idl_next)
|
|
{
|
|
if (index->idl_id == id) {
|
|
return index;
|
|
}
|
|
}
|
|
|
|
IndexLock* index = FB_NEW(*dbb->dbb_permanent) IndexLock();
|
|
index->idl_next = relation->rel_index_locks;
|
|
relation->rel_index_locks = index;
|
|
index->idl_relation = relation;
|
|
index->idl_id = id;
|
|
index->idl_count = 0;
|
|
|
|
Lock* lock = FB_NEW_RPT(*dbb->dbb_permanent, 0) Lock;
|
|
index->idl_lock = lock;
|
|
lock->lck_parent = dbb->dbb_lock;
|
|
lock->lck_dbb = dbb;
|
|
lock->lck_key.lck_long = (relation->rel_id << 16) | id;
|
|
lock->lck_length = sizeof(lock->lck_key.lck_long);
|
|
lock->lck_type = LCK_idx_exist;
|
|
lock->lck_owner_handle = LCK_get_owner_handle(tdbb, lock->lck_type);
|
|
|
|
return index;
|
|
}
|
|
|
|
|
|
ULONG CMP_impure(CompilerScratch* csb, ULONG size)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ i m p u r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate space (offset) in request.
|
|
*
|
|
**************************************/
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
if (!csb)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const ULONG offset = FB_ALIGN(csb->csb_impure, FB_ALIGNMENT);
|
|
|
|
if (offset + size > JrdStatement::MAX_REQUEST_SIZE)
|
|
{
|
|
IBERROR(226); // msg 226: request size limit exceeded
|
|
}
|
|
|
|
csb->csb_impure = offset + size;
|
|
|
|
return offset;
|
|
}
|
|
|
|
|
|
void CMP_post_access(thread_db* tdbb,
|
|
CompilerScratch* csb,
|
|
const Firebird::MetaName& security_name,
|
|
SLONG view_id,
|
|
SecurityClass::flags_t mask,
|
|
SLONG type_name,
|
|
const Firebird::MetaName& name,
|
|
const Firebird::MetaName& r_name)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ p o s t _ a c c e s s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Post access to security class to request.
|
|
* We append the new security class to the existing list of
|
|
* security classes for that request.
|
|
*
|
|
**************************************/
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(view, type_rel);
|
|
|
|
// allow all access to internal requests
|
|
|
|
if (csb->csb_g_flags & (csb_internal | csb_ignore_perm))
|
|
return;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
AccessItem access(security_name, view_id, name, type_name, mask, r_name);
|
|
|
|
size_t i;
|
|
|
|
if (csb->csb_access.find(access, i))
|
|
{
|
|
return;
|
|
}
|
|
|
|
csb->csb_access.insert(i, access);
|
|
}
|
|
|
|
|
|
void CMP_post_resource( ResourceList* rsc_ptr, void* obj, Resource::rsc_s type, USHORT id)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ p o s t _ r e s o u r c e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Post a resource usage to the compiler scratch block.
|
|
*
|
|
**************************************/
|
|
// Initialize resource block
|
|
Resource resource(type, id, NULL, NULL, NULL);
|
|
switch (type)
|
|
{
|
|
case Resource::rsc_relation:
|
|
case Resource::rsc_index:
|
|
resource.rsc_rel = (jrd_rel*) obj;
|
|
break;
|
|
case Resource::rsc_procedure:
|
|
resource.rsc_prc = (jrd_prc*) obj;
|
|
break;
|
|
case Resource::rsc_collation:
|
|
resource.rsc_coll = (Collation*) obj;
|
|
break;
|
|
case Resource::rsc_function:
|
|
resource.rsc_fun = (Function*) obj;
|
|
break;
|
|
default:
|
|
BUGCHECK(220); // msg 220 unknown resource
|
|
break;
|
|
}
|
|
|
|
// Add it into list if not present already
|
|
size_t pos;
|
|
if (!rsc_ptr->find(resource, pos))
|
|
rsc_ptr->insert(pos, resource);
|
|
}
|
|
|
|
|
|
void CMP_decrement_prc_use_count(thread_db* tdbb, jrd_prc* procedure)
|
|
{
|
|
/*********************************************
|
|
*
|
|
* C M P _ d e c r e m e n t _ p r c _ u s e _ c o u n t
|
|
*
|
|
*********************************************
|
|
*
|
|
* Functional description
|
|
* decrement the procedure's use count
|
|
*
|
|
*********************************************/
|
|
// Actually, it's possible for procedures to have intermixed dependencies, so
|
|
// this routine can be called for the procedure which is being freed itself.
|
|
// Hence we should just silently ignore such a situation.
|
|
|
|
if (!procedure->prc_use_count)
|
|
return;
|
|
|
|
if (procedure->prc_int_use_count > 0)
|
|
procedure->prc_int_use_count--;
|
|
|
|
--procedure->prc_use_count;
|
|
|
|
#ifdef DEBUG_PROCS
|
|
{
|
|
string buffer;
|
|
buffer.printf(
|
|
"Called from CMP_decrement():\n\t Decrementing use count of %s\n",
|
|
procedure->getName()->toString().c_str());
|
|
JRD_print_procedure_info(tdbb, buffer.c_str());
|
|
}
|
|
#endif
|
|
|
|
// Call recursively if and only if the use count is zero AND the procedure
|
|
// in dbb_procedures is different than this procedure.
|
|
// The procedure will be different than in dbb_procedures only if it is a
|
|
// floating copy, i.e. an old copy or a deleted procedure.
|
|
if ((procedure->prc_use_count == 0) &&
|
|
( (*tdbb->getDatabase()->dbb_procedures)[procedure->getId()] != procedure))
|
|
{
|
|
if (procedure->getStatement())
|
|
{
|
|
procedure->getStatement()->release(tdbb);
|
|
procedure->setStatement(NULL);
|
|
}
|
|
procedure->prc_flags &= ~PRC_being_altered;
|
|
MET_remove_procedure(tdbb, procedure->getId(), procedure);
|
|
}
|
|
}
|
|
|
|
|
|
void CMP_release(thread_db* tdbb, jrd_req* request)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ r e l e a s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Release a request's statement.
|
|
*
|
|
**************************************/
|
|
DEV_BLKCHK(request, type_req);
|
|
request->getStatement()->release(tdbb);
|
|
}
|
|
|
|
|
|
void CMP_shutdown_database(thread_db* tdbb)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ s h u t d o w n _ d a t a b a s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Release compile-time locks for database.
|
|
* Since this can be called at AST level, don't
|
|
* release any data structures.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
Database* dbb = tdbb->getDatabase();
|
|
CHECK_DBB(dbb);
|
|
|
|
DEV_BLKCHK(dbb, type_dbb);
|
|
|
|
// go through relations and indices and release
|
|
// all existence locks that might have been taken
|
|
|
|
vec<jrd_rel*>* rvector = dbb->dbb_relations;
|
|
if (rvector)
|
|
{
|
|
vec<jrd_rel*>::iterator ptr, end;
|
|
for (ptr = rvector->begin(), end = rvector->end(); ptr < end; ++ptr)
|
|
{
|
|
jrd_rel* relation = *ptr;
|
|
if (relation)
|
|
{
|
|
if (relation->rel_existence_lock)
|
|
{
|
|
LCK_release(tdbb, relation->rel_existence_lock);
|
|
relation->rel_flags |= REL_check_existence;
|
|
relation->rel_use_count = 0;
|
|
}
|
|
if (relation->rel_partners_lock)
|
|
{
|
|
LCK_release(tdbb, relation->rel_partners_lock);
|
|
relation->rel_flags |= REL_check_partners;
|
|
}
|
|
if (relation->rel_rescan_lock)
|
|
{
|
|
LCK_release(tdbb, relation->rel_rescan_lock);
|
|
relation->rel_flags &= ~REL_scanned;
|
|
}
|
|
for (IndexLock* index = relation->rel_index_locks; index; index = index->idl_next)
|
|
{
|
|
if (index->idl_lock)
|
|
{
|
|
index->idl_count = 0;
|
|
LCK_release(tdbb, index->idl_lock);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// release all procedure existence locks that might have been taken
|
|
|
|
vec<jrd_prc*>* pvector = dbb->dbb_procedures;
|
|
if (pvector)
|
|
{
|
|
vec<jrd_prc*>::iterator pptr, pend;
|
|
for (pptr = pvector->begin(), pend = pvector->end(); pptr < pend; ++pptr)
|
|
{
|
|
jrd_prc* procedure = *pptr;
|
|
if (procedure)
|
|
{
|
|
if (procedure->prc_existence_lock)
|
|
{
|
|
LCK_release(tdbb, procedure->prc_existence_lock);
|
|
procedure->prc_flags |= PRC_check_existence;
|
|
procedure->prc_use_count = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// release all function existence locks that might have been taken
|
|
|
|
for (Function** iter = dbb->dbb_functions.begin(); iter < dbb->dbb_functions.end(); ++iter)
|
|
{
|
|
Function* const function = *iter;
|
|
|
|
if (function)
|
|
{
|
|
function->releaseLocks(tdbb);
|
|
}
|
|
}
|
|
|
|
// release collation existence locks
|
|
|
|
dbb->releaseIntlObjects();
|
|
}
|
|
|
|
|
|
UCHAR* CMP_alloc_map(thread_db* tdbb, CompilerScratch* csb, USHORT stream)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ a l l o c _ m a p
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Allocate and initialize stream map for view processing.
|
|
*
|
|
**************************************/
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
fb_assert(stream <= MAX_STREAMS); // CVC: MAX_UCHAR maybe?
|
|
UCHAR* const p = FB_NEW(*tdbb->getDefaultPool()) UCHAR[JrdStatement::MAP_LENGTH];
|
|
memset(p, 0, JrdStatement::MAP_LENGTH);
|
|
p[0] = (UCHAR) stream;
|
|
csb->csb_rpt[stream].csb_map = p;
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
USHORT NodeCopier::getFieldId(FieldNode* field)
|
|
{
|
|
return field->fieldId;
|
|
}
|
|
|
|
// Copy an expression tree remapping field streams. If the map isn't present, don't remap.
|
|
jrd_nod* NodeCopier::copy(thread_db* tdbb, jrd_nod* input)
|
|
{
|
|
jrd_nod* node;
|
|
USHORT stream;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(input, type_nod);
|
|
|
|
if (!input)
|
|
return NULL;
|
|
|
|
// special case interesting nodes
|
|
|
|
USHORT args = input->nod_count;
|
|
|
|
switch (input->nod_type)
|
|
{
|
|
case nod_class_exprnode_jrd:
|
|
{
|
|
ExprNode* exprNode = reinterpret_cast<ExprNode*>(input->nod_arg[0]);
|
|
ExprNode* copy = exprNode->copy(tdbb, *this);
|
|
|
|
if (copy == exprNode)
|
|
node = input;
|
|
else
|
|
{
|
|
copy->nodFlags = exprNode->nodFlags;
|
|
|
|
node = PAR_make_node(tdbb, 1);
|
|
node->nod_type = input->nod_type;
|
|
node->nod_count = input->nod_count;
|
|
node->nod_arg[0] = reinterpret_cast<jrd_nod*>(copy);
|
|
}
|
|
}
|
|
return node;
|
|
|
|
case nod_assignment:
|
|
args = e_asgn_length;
|
|
break;
|
|
|
|
case nod_erase:
|
|
args = e_erase_length;
|
|
break;
|
|
|
|
case nod_modify:
|
|
args = e_mod_length;
|
|
break;
|
|
|
|
case nod_init_variable:
|
|
if (csb->csb_remap_variable != 0)
|
|
{
|
|
node = PAR_make_node(tdbb, e_init_var_length);
|
|
node->nod_type = input->nod_type;
|
|
node->nod_count = input->nod_count;
|
|
|
|
USHORT n = csb->csb_remap_variable + (USHORT)(IPTR) input->nod_arg[e_init_var_id];
|
|
node->nod_arg[e_init_var_id] = (jrd_nod*)(IPTR) n;
|
|
node->nod_arg[e_init_var_variable] = input->nod_arg[e_init_var_variable];
|
|
node->nod_arg[e_init_var_info] = input->nod_arg[e_init_var_info];
|
|
return node;
|
|
}
|
|
return input;
|
|
|
|
case nod_class_recsrcnode_jrd:
|
|
node = PAR_make_node(tdbb, 1);
|
|
node->nod_type = input->nod_type;
|
|
node->nod_count = input->nod_count;
|
|
node->nod_arg[0] = reinterpret_cast<jrd_nod*>(
|
|
reinterpret_cast<RecordSourceNode*>(input->nod_arg[0])->copy(tdbb, *this));
|
|
return node;
|
|
|
|
case nod_message:
|
|
node = PAR_make_node(tdbb, e_msg_length);
|
|
node->nod_type = input->nod_type;
|
|
node->nod_count = input->nod_count;
|
|
node->nod_arg[e_msg_number] = input->nod_arg[e_msg_number];
|
|
node->nod_arg[e_msg_format] = input->nod_arg[e_msg_format];
|
|
node->nod_arg[e_msg_impure_flags] = input->nod_arg[e_msg_impure_flags];
|
|
return node;
|
|
|
|
case nod_dcl_variable:
|
|
if (csb->csb_remap_variable != 0)
|
|
{
|
|
node = PAR_make_node(tdbb, e_dcl_length);
|
|
node->nod_type = input->nod_type;
|
|
node->nod_count = input->nod_count;
|
|
|
|
const USHORT n = csb->csb_remap_variable + (USHORT)(IPTR) input->nod_arg[e_dcl_id];
|
|
node->nod_arg[e_dcl_id] = (jrd_nod*)(IPTR) n;
|
|
*(dsc*) (node->nod_arg + e_dcl_desc) = *(dsc*) (input->nod_arg + e_dcl_desc);
|
|
|
|
csb->csb_variables =
|
|
vec<jrd_nod*>::newVector(*tdbb->getDefaultPool(), csb->csb_variables, n + 1);
|
|
|
|
return node;
|
|
}
|
|
return input;
|
|
|
|
case nod_dcl_cursor:
|
|
node = PAR_make_node(tdbb, e_dcl_cur_length);
|
|
node->nod_count = input->nod_count;
|
|
node->nod_type = input->nod_type;
|
|
node->nod_arg[e_dcl_cur_rse] = copy(tdbb, input->nod_arg[e_dcl_cur_rse]);
|
|
node->nod_arg[e_dcl_cur_refs] = copy(tdbb, input->nod_arg[e_dcl_cur_refs]);
|
|
node->nod_arg[e_dcl_cur_number] = input->nod_arg[e_dcl_cur_number];
|
|
break;
|
|
|
|
case nod_cursor_stmt:
|
|
node = PAR_make_node(tdbb, e_cursor_stmt_length);
|
|
node->nod_count = input->nod_count;
|
|
node->nod_type = input->nod_type;
|
|
node->nod_arg[e_cursor_stmt_op] = input->nod_arg[e_cursor_stmt_op];
|
|
node->nod_arg[e_cursor_stmt_number] = input->nod_arg[e_cursor_stmt_number];
|
|
node->nod_arg[e_cursor_stmt_scroll_op] = input->nod_arg[e_cursor_stmt_scroll_op];
|
|
node->nod_arg[e_cursor_stmt_scroll_val] = copy(tdbb, input->nod_arg[e_cursor_stmt_scroll_val]);
|
|
node->nod_arg[e_cursor_stmt_into] = copy(tdbb, input->nod_arg[e_cursor_stmt_into]);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// fall thru on generic nodes
|
|
|
|
node = PAR_make_node(tdbb, args);
|
|
node->nod_count = input->nod_count;
|
|
node->nod_type = input->nod_type;
|
|
|
|
jrd_nod** arg1 = input->nod_arg;
|
|
jrd_nod** arg2 = node->nod_arg;
|
|
|
|
for (const jrd_nod* const* const end = arg1 + input->nod_count; arg1 < end; arg1++, arg2++)
|
|
{
|
|
if (*arg1)
|
|
*arg2 = copy(tdbb, *arg1);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
// Expand dbkey for view.
|
|
void CMP_expand_view_nodes(thread_db* tdbb, CompilerScratch* csb, USHORT stream,
|
|
ValueExprNodeStack& stack, UCHAR blrOp, bool allStreams)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
// if the stream's dbkey should be ignored, do so
|
|
|
|
if (!allStreams && (csb->csb_rpt[stream].csb_flags & csb_no_dbkey))
|
|
return;
|
|
|
|
// if the stream references a view, follow map
|
|
const UCHAR* map = csb->csb_rpt[stream].csb_map;
|
|
if (map)
|
|
{
|
|
++map;
|
|
while (*map)
|
|
CMP_expand_view_nodes(tdbb, csb, *map++, stack, blrOp, allStreams);
|
|
return;
|
|
}
|
|
|
|
// relation is primitive - make dbkey node
|
|
|
|
if (allStreams || csb->csb_rpt[stream].csb_relation)
|
|
{
|
|
RecordKeyNode* node = FB_NEW(csb->csb_pool) RecordKeyNode(csb->csb_pool, blrOp);
|
|
node->recStream = stream;
|
|
stack.push(node);
|
|
}
|
|
}
|
|
|
|
|
|
static jrd_nod* make_defaults(thread_db* tdbb, CompilerScratch* csb, USHORT stream, jrd_nod* statement)
|
|
{
|
|
/**************************************
|
|
*
|
|
* m a k e _ d e f a u l t s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Build an default value assignments.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(statement, type_nod);
|
|
|
|
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
|
|
|
|
vec<jrd_fld*>* vector = relation->rel_fields;
|
|
if (!vector)
|
|
return statement;
|
|
|
|
UCHAR local_map[JrdStatement::MAP_LENGTH];
|
|
UCHAR* map = csb->csb_rpt[stream].csb_map;
|
|
if (!map)
|
|
{
|
|
map = local_map;
|
|
fb_assert(stream <= MAX_STREAMS); // CVC: MAX_UCHAR relevant, too?
|
|
map[0] = (UCHAR) stream;
|
|
map[1] = 1;
|
|
map[2] = 2;
|
|
}
|
|
|
|
NodeStack stack;
|
|
|
|
USHORT field_id = 0;
|
|
vec<jrd_fld*>::iterator ptr1 = vector->begin();
|
|
for (const vec<jrd_fld*>::const_iterator end = vector->end(); ptr1 < end; ++ptr1, ++field_id)
|
|
{
|
|
ValueExprNode* value;
|
|
|
|
if (!*ptr1 || !((*ptr1)->fld_generator_name.hasData() || (value = (*ptr1)->fld_default_value)))
|
|
continue;
|
|
|
|
fb_assert(statement->nod_type == nod_list);
|
|
if (statement->nod_type == nod_list)
|
|
{
|
|
bool inList = false;
|
|
|
|
for (unsigned i = 0; i < statement->nod_count; ++i)
|
|
{
|
|
const jrd_nod* assign = statement->nod_arg[i];
|
|
|
|
fb_assert(assign->nod_type == nod_assignment);
|
|
if (assign->nod_type == nod_assignment)
|
|
{
|
|
const jrd_nod* to = assign->nod_arg[e_asgn_to];
|
|
|
|
const FieldNode* fieldNode = ExprNode::as<FieldNode>(to);
|
|
fb_assert(fieldNode);
|
|
|
|
if (fieldNode && fieldNode->fieldStream == stream && fieldNode->fieldId == field_id)
|
|
{
|
|
inList = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (inList)
|
|
continue;
|
|
|
|
jrd_nod* node = PAR_make_node(tdbb, e_asgn_length);
|
|
node->nod_type = nod_assignment;
|
|
node->nod_arg[e_asgn_to] = PAR_gen_field(tdbb, stream, field_id);
|
|
stack.push(node);
|
|
|
|
if ((*ptr1)->fld_generator_name.hasData())
|
|
{
|
|
// Make a gen_id(<generator name>, 1) expression.
|
|
|
|
LiteralNode* literal = FB_NEW(csb->csb_pool) LiteralNode(csb->csb_pool);
|
|
SLONG* increment = FB_NEW(csb->csb_pool) SLONG(1);
|
|
literal->litDesc.makeLong(0, increment);
|
|
|
|
GenIdNode* genNode = FB_NEW(csb->csb_pool) GenIdNode(csb->csb_pool,
|
|
(csb->csb_g_flags & csb_blr_version4), (*ptr1)->fld_generator_name);
|
|
genNode->id = MET_lookup_generator(tdbb, (*ptr1)->fld_generator_name.c_str());
|
|
genNode->arg = literal;
|
|
|
|
jrd_nod* genNod = PAR_make_node(tdbb, 1);
|
|
genNod->nod_type = nod_class_exprnode_jrd;
|
|
genNod->nod_count = 0;
|
|
genNod->nod_arg[0] = reinterpret_cast<jrd_nod*>(genNode);
|
|
node->nod_arg[e_asgn_from] = genNod;
|
|
}
|
|
else //if (value)
|
|
{
|
|
// Clone the field default value.
|
|
|
|
jrd_nod* nod = PAR_make_node(tdbb, 1);
|
|
nod->nod_type = nod_class_exprnode_jrd;
|
|
nod->nod_count = 0;
|
|
nod->nod_arg[0] = reinterpret_cast<jrd_nod*>(
|
|
RemapFieldNodeCopier(csb, map, field_id).copy(tdbb, value));
|
|
|
|
node->nod_arg[e_asgn_from] = nod;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stack.isEmpty())
|
|
return statement;
|
|
|
|
// We have some default - add the original statement and make a list out of the whole mess.
|
|
|
|
stack.push(statement);
|
|
|
|
return PAR_make_list(tdbb, stack);
|
|
}
|
|
|
|
|
|
static jrd_nod* make_validation(thread_db* tdbb, CompilerScratch* csb, USHORT stream)
|
|
{
|
|
/**************************************
|
|
*
|
|
* m a k e _ v a l i d a t i o n
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Build a validation list for a relation, if appropriate.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
|
|
|
|
vec<jrd_fld*>* vector = relation->rel_fields;
|
|
if (!vector)
|
|
return NULL;
|
|
|
|
UCHAR local_map[JrdStatement::MAP_LENGTH];
|
|
UCHAR* map = csb->csb_rpt[stream].csb_map;
|
|
if (!map)
|
|
{
|
|
map = local_map;
|
|
fb_assert(stream <= MAX_STREAMS); // CVC: MAX_UCHAR still relevant for the bitmap?
|
|
map[0] = (UCHAR) stream;
|
|
}
|
|
|
|
NodeStack stack;
|
|
|
|
USHORT field_id = 0;
|
|
vec<jrd_fld*>::iterator ptr1 = vector->begin();
|
|
for (const vec<jrd_fld*>::const_iterator end = vector->end(); ptr1 < end; ++ptr1, ++field_id)
|
|
{
|
|
BoolExprNode* validation;
|
|
jrd_nod* validationStmt;
|
|
|
|
if (*ptr1 && (validation = (*ptr1)->fld_validation))
|
|
{
|
|
AutoSetRestore<USHORT> autoRemapVariable(&csb->csb_remap_variable,
|
|
(csb->csb_variables ? csb->csb_variables->count() : 0) + 1);
|
|
|
|
RemapFieldNodeCopier copier(csb, map, field_id);
|
|
|
|
if ((validationStmt = (*ptr1)->fld_validation_stmt))
|
|
validationStmt = copier.copy(tdbb, validationStmt);
|
|
|
|
validation = copier.copy(tdbb, validation);
|
|
|
|
jrd_nod* boolNod = PAR_make_node(tdbb, 1);
|
|
boolNod->nod_type = nod_class_exprnode_jrd;
|
|
boolNod->nod_count = 0;
|
|
boolNod->nod_arg[0] = reinterpret_cast<jrd_nod*>(validation);
|
|
|
|
jrd_nod* node = PAR_make_node(tdbb, e_val_length);
|
|
node->nod_type = nod_validate;
|
|
node->nod_arg[e_val_stmt] = validationStmt;
|
|
node->nod_arg[e_val_boolean] = boolNod;
|
|
node->nod_arg[e_val_value] = PAR_gen_field(tdbb, stream, field_id);
|
|
stack.push(node);
|
|
}
|
|
|
|
if (*ptr1 && (validation = (*ptr1)->fld_not_null))
|
|
{
|
|
AutoSetRestore<USHORT> autoRemapVariable(&csb->csb_remap_variable,
|
|
(csb->csb_variables ? csb->csb_variables->count() : 0) + 1);
|
|
|
|
RemapFieldNodeCopier copier(csb, map, field_id);
|
|
|
|
if ((validationStmt = (*ptr1)->fld_not_null_stmt))
|
|
validationStmt = copier.copy(tdbb, validationStmt);
|
|
|
|
validation = copier.copy(tdbb, validation);
|
|
|
|
jrd_nod* boolNod = PAR_make_node(tdbb, 1);
|
|
boolNod->nod_type = nod_class_exprnode_jrd;
|
|
boolNod->nod_count = 0;
|
|
boolNod->nod_arg[0] = reinterpret_cast<jrd_nod*>(validation);
|
|
|
|
jrd_nod* node = PAR_make_node(tdbb, e_val_length);
|
|
node->nod_type = nod_validate;
|
|
node->nod_arg[e_val_stmt] = validationStmt;
|
|
node->nod_arg[e_val_boolean] = boolNod;
|
|
node->nod_arg[e_val_value] = PAR_gen_field(tdbb, stream, field_id);
|
|
stack.push(node);
|
|
}
|
|
}
|
|
|
|
if (stack.isEmpty())
|
|
return NULL;
|
|
|
|
return PAR_make_list(tdbb, stack);
|
|
}
|
|
|
|
|
|
// Look at all RseNode's which are lower in scope than the RseNode which this field is
|
|
// referencing, and mark them as variant - the rule is that if a field from one RseNode is
|
|
// referenced within the scope of another RseNode, the inner RseNode can't be invariant.
|
|
// This won't optimize all cases, but it is the simplest operating assumption for now.
|
|
void CMP_mark_variant(CompilerScratch* csb, USHORT stream)
|
|
{
|
|
if (csb->csb_current_nodes.isEmpty())
|
|
return;
|
|
|
|
for (RseOrExprNode* node = csb->csb_current_nodes.end() - 1;
|
|
node != csb->csb_current_nodes.begin(); --node)
|
|
{
|
|
if (node->rseNode)
|
|
{
|
|
if (node->rseNode->containsStream(stream))
|
|
break;
|
|
node->rseNode->flags |= RseNode::FLAG_VARIANT;
|
|
}
|
|
else if (node->exprNode)
|
|
node->exprNode->nodFlags &= ~ExprNode::FLAG_INVARIANT;
|
|
}
|
|
}
|
|
|
|
|
|
ValueExprNode* CMP_pass1(thread_db* tdbb, CompilerScratch* csb, ValueExprNode* node)
|
|
{
|
|
if (!node)
|
|
return NULL;
|
|
|
|
return node->pass1(tdbb, csb);
|
|
}
|
|
|
|
BoolExprNode* CMP_pass1(thread_db* tdbb, CompilerScratch* csb, BoolExprNode* node)
|
|
{
|
|
if (!node)
|
|
return NULL;
|
|
|
|
return node->pass1(tdbb, csb);
|
|
}
|
|
|
|
jrd_nod* CMP_pass1(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ p a s s 1
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Merge missing values, computed values, validation expressions,
|
|
* and views into a parsed request.
|
|
*
|
|
* The csb->csb_validate_expr becomes true if an ancestor of the
|
|
* current node (the one being passed in) in the parse tree has nod_type
|
|
* == nod_validate. "ancestor" does not include the current node
|
|
* being passed in as an argument.
|
|
* If we are in a "validate subtree" (as determined by the
|
|
* csb->csb_validate_expr), we must not post update access to the fields involved
|
|
* in the validation clause. (see the call for CMP_post_access in this
|
|
* function.)
|
|
*
|
|
**************************************/
|
|
ValueExprNode* sub;
|
|
jrd_nod** ptr;
|
|
USHORT stream;
|
|
CompilerScratch::csb_repeat* tail;
|
|
jrd_prc* procedure;
|
|
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
if (!node)
|
|
return node;
|
|
|
|
AutoSetRestore<bool> autoValidateExpr(&csb->csb_validate_expr,
|
|
csb->csb_validate_expr || node->nod_type == nod_validate);
|
|
|
|
// if there is processing to be done before sub expressions, do it here
|
|
|
|
switch (node->nod_type)
|
|
{
|
|
case nod_init_variable:
|
|
{
|
|
const USHORT n = (USHORT)(IPTR) node->nod_arg[e_init_var_id];
|
|
vec<jrd_nod*>* vector = csb->csb_variables;
|
|
if (!vector || n >= vector->count() || !(node->nod_arg[e_init_var_variable] = (*vector)[n]))
|
|
{
|
|
PAR_syntax_error(csb, "variable identifier");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nod_assignment:
|
|
{
|
|
sub = node->nod_arg[e_asgn_from]->asValue();
|
|
FieldNode* fieldNode;
|
|
|
|
if ((fieldNode = sub->as<FieldNode>()))
|
|
{
|
|
stream = fieldNode->fieldStream;
|
|
jrd_fld* field = MET_get_field(csb->csb_rpt[stream].csb_relation, fieldNode->fieldId);
|
|
|
|
if (field)
|
|
{
|
|
jrd_nod* nod = field->fld_missing_value ? PAR_make_node(tdbb, 1) : NULL;
|
|
|
|
if (nod)
|
|
{
|
|
nod->nod_count = 0;
|
|
nod->nod_type = nod_class_exprnode_jrd;
|
|
nod->nod_arg[0] = reinterpret_cast<jrd_nod*>(field->fld_missing_value);
|
|
}
|
|
|
|
node->nod_arg[e_asgn_missing2] = nod;
|
|
}
|
|
}
|
|
|
|
sub = node->nod_arg[e_asgn_to]->asValue();
|
|
if (!(fieldNode = sub->as<FieldNode>()))
|
|
break;
|
|
|
|
stream = fieldNode->fieldStream;
|
|
tail = &csb->csb_rpt[stream];
|
|
jrd_fld* field = MET_get_field(tail->csb_relation, fieldNode->fieldId);
|
|
|
|
if (!field)
|
|
break;
|
|
|
|
if (field->fld_missing_value)
|
|
{
|
|
jrd_nod* nod = PAR_make_node(tdbb, 1);
|
|
nod->nod_count = 0;
|
|
nod->nod_type = nod_class_exprnode_jrd;
|
|
nod->nod_arg[0] = reinterpret_cast<jrd_nod*>(field->fld_missing_value);
|
|
|
|
node->nod_arg[e_asgn_missing] = nod;
|
|
node->nod_count = 3;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nod_modify:
|
|
pass1_modify(tdbb, csb, node);
|
|
break;
|
|
|
|
case nod_erase:
|
|
pass1_erase(tdbb, csb, node);
|
|
break;
|
|
|
|
case nod_exec_proc:
|
|
procedure = (jrd_prc*) node->nod_arg[e_esp_procedure];
|
|
// Post access to procedure
|
|
CMP_post_procedure_access(tdbb, csb, procedure);
|
|
CMP_post_resource(&csb->csb_resources, procedure,
|
|
Resource::rsc_procedure, procedure->getId());
|
|
break;
|
|
|
|
case nod_store:
|
|
if (pass1_store(tdbb, csb, node))
|
|
{
|
|
fb_assert(node->nod_arg[e_sto_relation]->nod_type == nod_class_recsrcnode_jrd);
|
|
RelationSourceNode* recSource = reinterpret_cast<RelationSourceNode*>(
|
|
node->nod_arg[e_sto_relation]->nod_arg[0]);
|
|
fb_assert(recSource->type == RelationSourceNode::TYPE);
|
|
|
|
stream = recSource->getStream();
|
|
node->nod_arg[e_sto_statement] =
|
|
make_defaults(tdbb, csb, stream, node->nod_arg[e_sto_statement]);
|
|
}
|
|
break;
|
|
|
|
case nod_cursor_stmt:
|
|
node->nod_arg[e_cursor_stmt_scroll_val] = CMP_pass1(tdbb, csb, node->nod_arg[e_cursor_stmt_scroll_val]);
|
|
node->nod_arg[e_cursor_stmt_into] = CMP_pass1(tdbb, csb, node->nod_arg[e_cursor_stmt_into]);
|
|
break;
|
|
|
|
case nod_class_recsrcnode_jrd:
|
|
reinterpret_cast<RecordSourceNode*>(node->nod_arg[0])->pass1(tdbb, csb);
|
|
break;
|
|
|
|
case nod_src_info:
|
|
node->nod_arg[e_src_info_node] = CMP_pass1(tdbb, csb, node->nod_arg[e_src_info_node]);
|
|
return node;
|
|
|
|
case nod_class_exprnode_jrd:
|
|
{
|
|
ExprNode* exprNode = reinterpret_cast<ExprNode*>(node->nod_arg[0]);
|
|
node->nod_arg[0] = reinterpret_cast<jrd_nod*>(exprNode->pass1(tdbb, csb));
|
|
}
|
|
return node;
|
|
|
|
case nod_class_stmtnode_jrd:
|
|
{
|
|
StmtNode* stmtNode = reinterpret_cast<StmtNode*>(node->nod_arg[0]);
|
|
stmtNode->setNode(node);
|
|
node->nod_arg[0] = reinterpret_cast<jrd_nod*>(stmtNode->pass1(tdbb, csb));
|
|
}
|
|
return node;
|
|
|
|
case nod_dcl_variable:
|
|
{
|
|
const USHORT n = (USHORT)(IPTR) node->nod_arg[e_dcl_id];
|
|
vec<jrd_nod*>* vector = csb->csb_variables =
|
|
vec<jrd_nod*>::newVector(*tdbb->getDefaultPool(), csb->csb_variables, n + 1);
|
|
fb_assert(!(*vector)[n]);
|
|
(*vector)[n] = node;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// handle sub-expressions here
|
|
|
|
ptr = node->nod_arg;
|
|
|
|
for (const jrd_nod* const* const end = ptr + node->nod_count; ptr < end; ptr++) {
|
|
*ptr = CMP_pass1(tdbb, csb, *ptr);
|
|
}
|
|
|
|
// perform any post-processing here
|
|
|
|
if (node->nod_type == nod_assignment)
|
|
{
|
|
sub = node->nod_arg[e_asgn_to]->asValue();
|
|
FieldNode* fieldNode;
|
|
|
|
if ((fieldNode = sub->as<FieldNode>()))
|
|
{
|
|
stream = fieldNode->fieldStream;
|
|
tail = &csb->csb_rpt[stream];
|
|
|
|
// assignments to the OLD context are prohibited for all trigger types
|
|
if ((tail->csb_flags & csb_trigger) && stream == 0)
|
|
ERR_post(Arg::Gds(isc_read_only_field));
|
|
|
|
// assignments to the NEW context are prohibited for post-action triggers
|
|
if ((tail->csb_flags & csb_trigger) && stream == 1 &&
|
|
(csb->csb_g_flags & csb_post_trigger))
|
|
{
|
|
ERR_post(Arg::Gds(isc_read_only_field));
|
|
}
|
|
}
|
|
else if (!(sub->is<ParameterNode>() || sub->is<VariableNode>() || sub->is<NullNode>()))
|
|
ERR_post(Arg::Gds(isc_read_only_field));
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
static void pass1_erase(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a s s 1 _ e r a s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Checkout an erase statement. If it references a view, and
|
|
* is kosher, fix it up.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
// if updateable views with triggers are involved, there
|
|
// maybe a recursive call to be ignored
|
|
|
|
if (node->nod_arg[e_erase_sub_erase])
|
|
return;
|
|
|
|
// to support nested views, loop until we hit a table or
|
|
// a view with user-defined triggers (which means no update)
|
|
|
|
jrd_rel* parent = NULL;
|
|
jrd_rel* view = NULL;
|
|
USHORT parent_stream = 0;
|
|
|
|
for (;;)
|
|
{
|
|
USHORT new_stream = (USHORT)(IPTR) node->nod_arg[e_erase_stream];
|
|
const USHORT stream = new_stream;
|
|
|
|
CompilerScratch::csb_repeat* tail = &csb->csb_rpt[stream];
|
|
tail->csb_flags |= csb_erase;
|
|
|
|
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
|
|
view = (relation->rel_view_rse) ? relation : view;
|
|
if (!parent) {
|
|
parent = tail->csb_view;
|
|
}
|
|
|
|
post_trigger_access(csb, relation, ExternalAccess::exa_delete, view);
|
|
|
|
// Check out delete. If this is a delete thru a view, verify the
|
|
// view by checking for read access on the base table. If field-level select
|
|
// privileges are implemented, this needs to be enhanced.
|
|
|
|
SecurityClass::flags_t priv = SCL_sql_delete;
|
|
if (parent) {
|
|
priv |= SCL_read;
|
|
}
|
|
|
|
const trig_vec* trigger = relation->rel_pre_erase ?
|
|
relation->rel_pre_erase : relation->rel_post_erase;
|
|
|
|
// if we have a view with triggers, let's expand it
|
|
|
|
if (relation->rel_view_rse && trigger)
|
|
{
|
|
new_stream = csb->nextStream();
|
|
node->nod_arg[e_erase_stream] = (jrd_nod*) (IPTR) new_stream;
|
|
CMP_csb_element(csb, new_stream)->csb_relation = relation;
|
|
|
|
node->nod_arg[e_erase_statement] = pass1_expand_view(tdbb, csb, stream, new_stream, false);
|
|
node->nod_count = MAX(node->nod_count, (USHORT) e_erase_statement + 1);
|
|
}
|
|
|
|
// get the source relation, either a table or yet another view
|
|
|
|
RelationSourceNode* source = pass1_update(tdbb, csb, relation, trigger, stream,
|
|
new_stream, priv, parent, parent_stream);
|
|
|
|
if (!source)
|
|
return; // no source means we're done
|
|
|
|
parent = relation;
|
|
parent_stream = stream;
|
|
|
|
// remap the source stream
|
|
|
|
UCHAR* map = csb->csb_rpt[stream].csb_map;
|
|
|
|
if (trigger)
|
|
{
|
|
// set up the new target stream
|
|
|
|
jrd_nod* view_node = NodeCopier::copy(tdbb, csb, node, map);
|
|
view_node->nod_arg[e_erase_statement] = NULL;
|
|
view_node->nod_arg[e_erase_sub_erase] = NULL;
|
|
|
|
node->nod_arg[e_erase_sub_erase] = view_node;
|
|
node->nod_count = MAX(node->nod_count, (USHORT) e_erase_sub_erase + 1);
|
|
|
|
// substitute the original delete node with the newly created one
|
|
node = view_node;
|
|
}
|
|
else
|
|
{
|
|
// this relation is not actually being updated as this operation
|
|
// goes deeper (we have a naturally updatable view)
|
|
csb->csb_rpt[new_stream].csb_flags &= ~csb_view_update;
|
|
}
|
|
|
|
// let's reset the target stream
|
|
new_stream = source->getStream();
|
|
node->nod_arg[e_erase_stream] = (jrd_nod*)(IPTR) map[new_stream];
|
|
}
|
|
}
|
|
|
|
|
|
static jrd_nod* pass1_expand_view(thread_db* tdbb,
|
|
CompilerScratch* csb,
|
|
USHORT org_stream,
|
|
USHORT new_stream,
|
|
bool remap)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a s s 1 _ e x p a n d _ v i e w
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Process a view update performed by a trigger.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
NodeStack stack;
|
|
jrd_rel* relation = csb->csb_rpt[org_stream].csb_relation;
|
|
vec<jrd_fld*>* fields = relation->rel_fields;
|
|
|
|
dsc desc;
|
|
USHORT id = 0, new_id = 0;
|
|
vec<jrd_fld*>::iterator ptr = fields->begin();
|
|
for (const vec<jrd_fld*>::const_iterator end = fields->end(); ptr < end; ++ptr, ++id)
|
|
{
|
|
if (*ptr)
|
|
{
|
|
if (remap)
|
|
{
|
|
const jrd_fld* field = MET_get_field(relation, id);
|
|
|
|
if (field->fld_source)
|
|
new_id = field->fld_source->as<FieldNode>()->fieldId;
|
|
else
|
|
new_id = id;
|
|
}
|
|
else
|
|
new_id = id;
|
|
|
|
jrd_nod* node = PAR_gen_field(tdbb, new_stream, new_id);
|
|
CMP_get_desc(tdbb, csb, node, &desc);
|
|
|
|
if (!desc.dsc_address)
|
|
{
|
|
delete node;
|
|
continue;
|
|
}
|
|
|
|
jrd_nod* assign = PAR_make_node(tdbb, e_asgn_length);
|
|
assign->nod_type = nod_assignment;
|
|
assign->nod_arg[e_asgn_to] = node;
|
|
assign->nod_arg[e_asgn_from] = PAR_gen_field(tdbb, org_stream, id);
|
|
stack.push(assign);
|
|
}
|
|
}
|
|
|
|
return PAR_make_list(tdbb, stack);
|
|
}
|
|
|
|
|
|
static void pass1_modify(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a s s 1 _ m o d i f y
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Process a source for a modify statement. This can
|
|
* get a little tricky if the relation is a view.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
// if updateable views with triggers are involved, there
|
|
// maybe a recursive call to be ignored
|
|
|
|
if (node->nod_arg[e_mod_sub_mod])
|
|
return;
|
|
|
|
jrd_rel* parent = NULL;
|
|
jrd_rel* view = NULL;
|
|
USHORT parent_stream = 0;
|
|
|
|
// to support nested views, loop until we hit a table or
|
|
// a view with user-defined triggers (which means no update)
|
|
|
|
for (;;)
|
|
{
|
|
USHORT stream = (USHORT)(IPTR) node->nod_arg[e_mod_org_stream];
|
|
USHORT new_stream = (USHORT)(IPTR) node->nod_arg[e_mod_new_stream];
|
|
|
|
CompilerScratch::csb_repeat* tail = &csb->csb_rpt[new_stream];
|
|
tail->csb_flags |= csb_modify;
|
|
|
|
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
|
|
view = (relation->rel_view_rse) ? relation : view;
|
|
if (!parent) {
|
|
parent = tail->csb_view;
|
|
}
|
|
|
|
post_trigger_access(csb, relation, ExternalAccess::exa_update, view);
|
|
|
|
// Check out update. If this is an update thru a view, verify the
|
|
// view by checking for read access on the base table. If field-level select
|
|
// privileges are implemented, this needs to be enhanced.
|
|
|
|
SecurityClass::flags_t priv = SCL_sql_update;
|
|
if (parent) {
|
|
priv |= SCL_read;
|
|
}
|
|
|
|
const trig_vec* trigger = (relation->rel_pre_modify) ?
|
|
relation->rel_pre_modify : relation->rel_post_modify;
|
|
|
|
// if we have a view with triggers, let's expand it
|
|
|
|
if (relation->rel_view_rse && trigger)
|
|
{
|
|
node->nod_arg[e_mod_map_view] = pass1_expand_view(tdbb, csb, stream, new_stream, false);
|
|
node->nod_count = MAX(node->nod_count, (USHORT) e_mod_map_view + 1);
|
|
}
|
|
|
|
// get the source relation, either a table or yet another view
|
|
|
|
RelationSourceNode* source = pass1_update(tdbb, csb, relation, trigger, stream,
|
|
new_stream, priv, parent, parent_stream);
|
|
|
|
if (!source)
|
|
{
|
|
// no source means we're done
|
|
|
|
if (!relation->rel_view_rse)
|
|
{
|
|
// apply validation constraints
|
|
|
|
if ( (node->nod_arg[e_mod_validate] = make_validation(tdbb, csb, new_stream)) )
|
|
node->nod_count = MAX(node->nod_count, (USHORT) e_mod_validate + 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
parent = relation;
|
|
parent_stream = stream;
|
|
|
|
// remap the source stream
|
|
|
|
UCHAR* map = csb->csb_rpt[stream].csb_map;
|
|
|
|
stream = source->getStream();
|
|
stream = map[stream];
|
|
|
|
// copy the view source
|
|
|
|
map = CMP_alloc_map(tdbb, csb, (SSHORT)(IPTR) node->nod_arg[e_mod_new_stream]);
|
|
NodeCopier copier(csb, map);
|
|
source = source->copy(tdbb, copier);
|
|
|
|
if (trigger)
|
|
{
|
|
// set up the new target stream
|
|
|
|
const USHORT view_stream = new_stream;
|
|
new_stream = source->getStream();
|
|
fb_assert(new_stream <= MAX_STREAMS);
|
|
map[view_stream] = new_stream;
|
|
|
|
jrd_nod* view_node = ViewNodeCopier(csb, map).copy(tdbb, node);
|
|
view_node->nod_arg[e_mod_map_view] = NULL;
|
|
view_node->nod_arg[e_mod_statement] =
|
|
pass1_expand_view(tdbb, csb, view_stream, new_stream, true);
|
|
|
|
node->nod_arg[e_mod_sub_mod] = view_node;
|
|
node->nod_count = MAX(node->nod_count, (USHORT) e_mod_sub_mod + 1);
|
|
|
|
// substitute the original update node with the newly created one
|
|
node = view_node;
|
|
}
|
|
else
|
|
{
|
|
// this relation is not actually being updated as this operation
|
|
// goes deeper (we have a naturally updatable view)
|
|
|
|
csb->csb_rpt[new_stream].csb_flags &= ~csb_view_update;
|
|
}
|
|
|
|
// let's reset streams to represent the mapped source and target
|
|
node->nod_arg[e_mod_org_stream] = (jrd_nod*)(IPTR) stream;
|
|
node->nod_arg[e_mod_new_stream] = (jrd_nod*)(IPTR) source->getStream();
|
|
}
|
|
}
|
|
|
|
|
|
static bool pass1_store(thread_db* tdbb, CompilerScratch* csb, jrd_nod* node)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a s s 1 _ s t o r e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Process a source for a store statement. This can get a little tricky if
|
|
* the relation is a view.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(node, type_nod);
|
|
|
|
// if updateable views with triggers are involved, there
|
|
// may be a recursive call to be ignored
|
|
|
|
if (node->nod_arg[e_sto_sub_store])
|
|
return false;
|
|
|
|
jrd_rel* parent = NULL;
|
|
jrd_rel* view = NULL;
|
|
USHORT parent_stream = 0;
|
|
|
|
// to support nested views, loop until we hit a table or
|
|
// a view with user-defined triggers (which means no update)
|
|
|
|
for (;;)
|
|
{
|
|
fb_assert(node->nod_arg[e_sto_relation]->nod_type == nod_class_recsrcnode_jrd);
|
|
RelationSourceNode* relSource = reinterpret_cast<RelationSourceNode*>(
|
|
node->nod_arg[e_sto_relation]->nod_arg[0]);
|
|
fb_assert(relSource->type == RelationSourceNode::TYPE);
|
|
|
|
const USHORT stream = relSource->getStream();
|
|
|
|
CompilerScratch::csb_repeat* tail = &csb->csb_rpt[stream];
|
|
tail->csb_flags |= csb_store;
|
|
|
|
jrd_rel* relation = csb->csb_rpt[stream].csb_relation;
|
|
view = (relation->rel_view_rse) ? relation : view;
|
|
if (!parent) {
|
|
parent = tail->csb_view;
|
|
}
|
|
|
|
post_trigger_access(csb, relation, ExternalAccess::exa_insert, view);
|
|
|
|
const trig_vec* trigger = (relation->rel_pre_store) ?
|
|
relation->rel_pre_store : relation->rel_post_store;
|
|
|
|
// Check out insert. If this is an insert thru a view, verify the
|
|
// view by checking for read access on the base table. If field-level select
|
|
// privileges are implemented, this needs to be enhanced.
|
|
|
|
SecurityClass::flags_t priv = SCL_sql_insert;
|
|
if (parent) {
|
|
priv |= SCL_read;
|
|
}
|
|
|
|
// get the source relation, either a table or yet another view
|
|
|
|
relSource = pass1_update(tdbb, csb, relation, trigger, stream, stream, priv,
|
|
parent, parent_stream);
|
|
|
|
if (!relSource)
|
|
{
|
|
CMP_post_resource(&csb->csb_resources, relation, Resource::rsc_relation, relation->rel_id);
|
|
|
|
if (!relation->rel_view_rse)
|
|
{
|
|
// apply validation constraints
|
|
if ( (node->nod_arg[e_sto_validate] = make_validation(tdbb, csb, stream)) )
|
|
node->nod_count = MAX(node->nod_count, (USHORT) e_sto_validate + 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
parent = relation;
|
|
parent_stream = stream;
|
|
|
|
UCHAR* map = CMP_alloc_map(tdbb, csb, stream);
|
|
NodeCopier copier(csb, map);
|
|
|
|
if (trigger)
|
|
{
|
|
CMP_post_resource(&csb->csb_resources, relation, Resource::rsc_relation, relation->rel_id);
|
|
|
|
// set up the new target stream
|
|
|
|
jrd_nod* view_node = NodeCopier::copy(tdbb, csb, node, map);
|
|
view_node->nod_arg[e_sto_sub_store] = NULL;
|
|
|
|
relSource = relSource->copy(tdbb, copier);
|
|
|
|
view_node->nod_arg[e_sto_relation] = PAR_make_node(tdbb, 1);
|
|
view_node->nod_arg[e_sto_relation]->nod_type = nod_class_recsrcnode_jrd;
|
|
view_node->nod_arg[e_sto_relation]->nod_count = 0;
|
|
view_node->nod_arg[e_sto_relation]->nod_arg[0] = reinterpret_cast<jrd_nod*>(relSource);
|
|
|
|
const USHORT new_stream = relSource->getStream();
|
|
view_node->nod_arg[e_sto_statement] =
|
|
pass1_expand_view(tdbb, csb, stream, new_stream, true);
|
|
|
|
// dimitr: I don't think the below code is required, but time will show
|
|
// view_node->nod_arg[e_sto_statement] =
|
|
// NodeCopier::copy(tdbb, csb, view_node->nod_arg[e_sto_statement], NULL);
|
|
|
|
// bug 8150: use of blr_store2 against a view with a trigger was causing
|
|
// the second statement to be executed, which is not desirable
|
|
|
|
view_node->nod_arg[e_sto_statement2] = NULL;
|
|
|
|
node->nod_arg[e_sto_sub_store] = view_node;
|
|
node->nod_count = MAX(node->nod_count, (USHORT) e_sto_sub_store + 1);
|
|
|
|
// substitute the original update node with the newly created one
|
|
node = view_node;
|
|
}
|
|
else
|
|
{
|
|
// this relation is not actually being updated as this operation
|
|
// goes deeper (we have a naturally updatable view)
|
|
|
|
csb->csb_rpt[stream].csb_flags &= ~csb_view_update;
|
|
|
|
relSource = relSource->copy(tdbb, copier);
|
|
|
|
node->nod_arg[e_sto_relation] = PAR_make_node(tdbb, 1);
|
|
node->nod_arg[e_sto_relation]->nod_type = nod_class_recsrcnode_jrd;
|
|
node->nod_arg[e_sto_relation]->nod_count = 0;
|
|
node->nod_arg[e_sto_relation]->nod_arg[0] = reinterpret_cast<jrd_nod*>(relSource);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static RelationSourceNode* pass1_update(thread_db* tdbb, CompilerScratch* csb, jrd_rel* relation,
|
|
const trig_vec* trigger, USHORT stream, USHORT update_stream, SecurityClass::flags_t priv,
|
|
jrd_rel* view, USHORT view_stream)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p a s s 1 _ u p d a t e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Check out a prospective update to a relation. If it fails
|
|
* security check, bounce it. If it's a view update, make sure
|
|
* the view is updatable, and return the view source for redirection.
|
|
* If it's a simple relation, return NULL.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(relation, type_rel);
|
|
DEV_BLKCHK(view, type_rel);
|
|
|
|
// unless this is an internal request, check access permission
|
|
|
|
CMP_post_access(tdbb, csb, relation->rel_security_name, (view ? view->rel_id : 0),
|
|
priv, SCL_object_table, relation->rel_name);
|
|
|
|
// ensure that the view is set for the input streams,
|
|
// so that access to views can be checked at the field level
|
|
|
|
fb_assert(view_stream <= MAX_STREAMS);
|
|
CMP_csb_element(csb, stream)->csb_view = view;
|
|
CMP_csb_element(csb, stream)->csb_view_stream = (UCHAR) view_stream;
|
|
CMP_csb_element(csb, update_stream)->csb_view = view;
|
|
CMP_csb_element(csb, update_stream)->csb_view_stream = (UCHAR) view_stream;
|
|
|
|
// if we're not a view, everything's cool
|
|
|
|
RseNode* rse = relation->rel_view_rse;
|
|
if (!rse)
|
|
return NULL;
|
|
|
|
// a view with triggers is always updatable
|
|
|
|
if (trigger)
|
|
{
|
|
bool user_triggers = false;
|
|
for (size_t i = 0; i < trigger->getCount(); i++)
|
|
{
|
|
if (!(*trigger)[i].sys_trigger)
|
|
{
|
|
user_triggers = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (user_triggers)
|
|
{
|
|
csb->csb_rpt[update_stream].csb_flags |= csb_view_update;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// we've got a view without triggers, let's check whether it's updateable
|
|
|
|
if (rse->rse_relations.getCount() != 1 || rse->rse_projection || rse->rse_sorted ||
|
|
rse->rse_relations[0]->type != RelationSourceNode::TYPE)
|
|
{
|
|
ERR_post(Arg::Gds(isc_read_only_view) << Arg::Str(relation->rel_name));
|
|
}
|
|
|
|
// for an updateable view, return the view source
|
|
|
|
csb->csb_rpt[update_stream].csb_flags |= csb_view_update;
|
|
|
|
return static_cast<RelationSourceNode*>(rse->rse_relations[0].getObject());
|
|
}
|
|
|
|
|
|
// Copy items' information into appropriate node.
|
|
ItemInfo* CMP_pass2_validation(thread_db* tdbb, CompilerScratch* csb, const Item& item)
|
|
{
|
|
ItemInfo itemInfo;
|
|
return csb->csb_map_item_info.get(item, itemInfo) ?
|
|
FB_NEW(*tdbb->getDefaultPool()) ItemInfo(*tdbb->getDefaultPool(), itemInfo) : NULL;
|
|
}
|
|
|
|
|
|
// Allocate and assign impure space for various nodes.
|
|
ExprNode* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, ExprNode* node)
|
|
{
|
|
NodeRefImpl<ExprNode> temp(&node);
|
|
temp.pass2(tdbb, csb);
|
|
return node;
|
|
}
|
|
|
|
// Allocate and assign impure space for various nodes.
|
|
ValueExprNode* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, ValueExprNode* node)
|
|
{
|
|
return static_cast<ValueExprNode*>(CMP_pass2(tdbb, csb, static_cast<ExprNode*>(node)));
|
|
}
|
|
|
|
// Allocate and assign impure space for various nodes.
|
|
BoolExprNode* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, BoolExprNode* node)
|
|
{
|
|
return static_cast<BoolExprNode*>(CMP_pass2(tdbb, csb, static_cast<ExprNode*>(node)));
|
|
}
|
|
|
|
// Allocate and assign impure space for various nodes.
|
|
jrd_nod* CMP_pass2(thread_db* tdbb, CompilerScratch* csb, jrd_nod* const node, jrd_nod* parent)
|
|
{
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(node, type_nod);
|
|
DEV_BLKCHK(parent, type_nod);
|
|
|
|
if (!node) {
|
|
return node;
|
|
}
|
|
|
|
if (parent) {
|
|
node->nod_parent = parent;
|
|
}
|
|
|
|
// if there is processing to be done before sub expressions, do it here
|
|
USHORT stream;
|
|
|
|
DEBUG;
|
|
RseNode* rse_node = NULL;
|
|
Cursor** cursor_ptr = NULL;
|
|
|
|
switch (node->nod_type)
|
|
{
|
|
case nod_class_stmtnode_jrd:
|
|
reinterpret_cast<StmtNode*>(node->nod_arg[0])->pass2Cursor(rse_node, cursor_ptr);
|
|
break;
|
|
|
|
case nod_dcl_cursor:
|
|
fb_assert(node->nod_arg[e_dcl_cur_rse]->nod_type == nod_class_recsrcnode_jrd);
|
|
rse_node = reinterpret_cast<RseNode*>(node->nod_arg[e_dcl_cur_rse]->nod_arg[0]);
|
|
fb_assert(rse_node->type == RseNode::TYPE);
|
|
cursor_ptr = (Cursor**) &node->nod_arg[e_dcl_cur_cursor];
|
|
break;
|
|
|
|
case nod_cursor_stmt:
|
|
CMP_pass2(tdbb, csb, node->nod_arg[e_cursor_stmt_scroll_val], node);
|
|
CMP_pass2(tdbb, csb, node->nod_arg[e_cursor_stmt_into], node);
|
|
break;
|
|
|
|
case nod_src_info:
|
|
node->nod_arg[e_src_info_node] = CMP_pass2(tdbb, csb, node->nod_arg[e_src_info_node], node);
|
|
return node;
|
|
|
|
case nod_init_variable:
|
|
node->nod_arg[e_init_var_info] = reinterpret_cast<jrd_nod*>(CMP_pass2_validation(tdbb,
|
|
csb, Item(Item::TYPE_VARIABLE, (IPTR) node->nod_arg[e_init_var_id])));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (rse_node)
|
|
rse_node->pass2Rse(tdbb, csb);
|
|
|
|
// handle sub-expressions here
|
|
|
|
// AB: Mark the streams involved with INSERT/UPDATE statements active.
|
|
// So that the optimizer can use indices for eventually used sub-selects.
|
|
if (node->nod_type == nod_modify)
|
|
{
|
|
stream = (USHORT)(IPTR) node->nod_arg[e_mod_org_stream];
|
|
csb->csb_rpt[stream].csb_flags |= csb_active;
|
|
stream = (USHORT)(IPTR) node->nod_arg[e_mod_new_stream];
|
|
csb->csb_rpt[stream].csb_flags |= csb_active;
|
|
}
|
|
else if (node->nod_type == nod_store)
|
|
{
|
|
fb_assert(node->nod_arg[e_sto_relation]->nod_type == nod_class_recsrcnode_jrd);
|
|
RelationSourceNode* recSource = reinterpret_cast<RelationSourceNode*>(
|
|
node->nod_arg[e_sto_relation]->nod_arg[0]);
|
|
fb_assert(recSource->type == RelationSourceNode::TYPE);
|
|
|
|
stream = recSource->getStream();
|
|
csb->csb_rpt[stream].csb_flags |= csb_active;
|
|
}
|
|
|
|
jrd_nod** ptr = node->nod_arg;
|
|
// This "end" is used later.
|
|
const jrd_nod* const* const end = ptr + node->nod_count;
|
|
|
|
for (; ptr < end; ptr++)
|
|
CMP_pass2(tdbb, csb, *ptr, node);
|
|
|
|
// AB: Remove the previous flags
|
|
if (node->nod_type == nod_modify)
|
|
{
|
|
stream = (USHORT)(IPTR) node->nod_arg[e_mod_org_stream];
|
|
csb->csb_rpt[stream].csb_flags &= ~csb_active;
|
|
stream = (USHORT)(IPTR) node->nod_arg[e_mod_new_stream];
|
|
csb->csb_rpt[stream].csb_flags &= ~csb_active;
|
|
}
|
|
else if (node->nod_type == nod_store)
|
|
{
|
|
fb_assert(node->nod_arg[e_sto_relation]->nod_type == nod_class_recsrcnode_jrd);
|
|
RelationSourceNode* recSource = reinterpret_cast<RelationSourceNode*>(
|
|
node->nod_arg[e_sto_relation]->nod_arg[0]);
|
|
fb_assert(recSource->type == RelationSourceNode::TYPE);
|
|
|
|
stream = recSource->getStream();
|
|
csb->csb_rpt[stream].csb_flags &= ~csb_active;
|
|
}
|
|
|
|
// Handle any residual work
|
|
|
|
switch (node->nod_type)
|
|
{
|
|
case nod_class_recsrcnode_jrd:
|
|
reinterpret_cast<RecordSourceNode*>(node->nod_arg[0])->pass2(tdbb, csb);
|
|
break;
|
|
|
|
case nod_assignment:
|
|
CMP_pass2(tdbb, csb, node->nod_arg[e_asgn_missing2], node);
|
|
break;
|
|
|
|
case nod_block:
|
|
node->nod_impure = CMP_impure(csb, sizeof(SLONG));
|
|
break;
|
|
|
|
case nod_dcl_variable:
|
|
{
|
|
const dsc* desc = (DSC*) (node->nod_arg + e_dcl_desc);
|
|
node->nod_impure = CMP_impure(csb, sizeof(impure_value) + desc->dsc_length);
|
|
}
|
|
break;
|
|
|
|
case nod_message:
|
|
{
|
|
const Format* format = (Format*) node->nod_arg[e_msg_format];
|
|
fb_assert(format);
|
|
node->nod_impure = CMP_impure(csb, FB_ALIGN(format->fmt_length, 2));
|
|
node->nod_arg[e_msg_impure_flags] = (jrd_nod*)(IPTR)
|
|
CMP_impure(csb, sizeof(USHORT) * format->fmt_count);
|
|
}
|
|
break;
|
|
|
|
case nod_modify:
|
|
{
|
|
stream = (USHORT)(IPTR) node->nod_arg[e_mod_org_stream];
|
|
csb->csb_rpt[stream].csb_flags |= csb_update;
|
|
const Format* format = CMP_format(tdbb, csb, stream);
|
|
Format::fmt_desc_const_iterator desc = format->fmt_desc.begin();
|
|
for (ULONG id = 0; id < format->fmt_count; ++id, ++desc)
|
|
{
|
|
if (desc->dsc_dtype) {
|
|
SBM_SET(tdbb->getDefaultPool(), &csb->csb_rpt[stream].csb_fields, id);
|
|
}
|
|
}
|
|
node->nod_impure = CMP_impure(csb, sizeof(impure_state));
|
|
}
|
|
break;
|
|
|
|
case nod_list:
|
|
// We are using the same "node" always. The "end" was set in the middle
|
|
// of the two huge switch() statements (this is the second).
|
|
if (node->nod_count > 0)
|
|
{
|
|
node->nod_type = nod_asn_list;
|
|
for (ptr = node->nod_arg; ptr < end; ptr++)
|
|
{
|
|
if (*ptr && (*ptr)->nod_type != nod_assignment)
|
|
{
|
|
node->nod_type = nod_list;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// FALL INTO
|
|
|
|
case nod_store:
|
|
node->nod_impure = CMP_impure(csb, sizeof(impure_state));
|
|
break;
|
|
|
|
case nod_erase:
|
|
stream = (USHORT)(IPTR) node->nod_arg[e_erase_stream];
|
|
csb->csb_rpt[stream].csb_flags |= csb_update;
|
|
break;
|
|
|
|
case nod_exec_into:
|
|
node->nod_impure = CMP_impure(csb, sizeof(ExecuteStatement));
|
|
csb->csb_exec_sta.push(node);
|
|
break;
|
|
|
|
case nod_exec_stmt:
|
|
node->nod_impure = CMP_impure(csb, sizeof(void**));
|
|
break;
|
|
|
|
case nod_class_exprnode_jrd:
|
|
{
|
|
ExprNode* exprNode = CMP_pass2(tdbb, csb, reinterpret_cast<ExprNode*>(node->nod_arg[0]));
|
|
node->nod_arg[0] = reinterpret_cast<jrd_nod*>(exprNode);
|
|
break;
|
|
}
|
|
|
|
case nod_class_stmtnode_jrd:
|
|
{
|
|
StmtNode* stmtNode = reinterpret_cast<StmtNode*>(node->nod_arg[0]);
|
|
stmtNode->setNode(node);
|
|
node->nod_arg[0] = reinterpret_cast<jrd_nod*>(stmtNode->pass2(tdbb, csb));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// note: no fb_assert(false); here as too many nodes are missing
|
|
break;
|
|
}
|
|
|
|
// finish up processing of record selection expressions
|
|
|
|
if (rse_node)
|
|
{
|
|
RecordSource* const rsb = CMP_post_rse(tdbb, csb, rse_node);
|
|
|
|
csb->csb_fors.add(rsb);
|
|
|
|
if (cursor_ptr)
|
|
{
|
|
Cursor* const cursor = FB_NEW(*tdbb->getDefaultPool()) Cursor(
|
|
csb, rsb, rse_node->rse_invariants, (rse_node->flags & RseNode::FLAG_SCROLLABLE));
|
|
*cursor_ptr = cursor;
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
void CMP_post_procedure_access(thread_db* tdbb, CompilerScratch* csb, jrd_prc* procedure)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ p o s t _ p r o c e d u r e _ a c c e s s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
*
|
|
* The request will inherit access requirements to all the objects
|
|
* the called stored procedure has access requirements for.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
|
|
// allow all access to internal requests
|
|
|
|
if (csb->csb_g_flags & (csb_internal | csb_ignore_perm))
|
|
return;
|
|
|
|
const TEXT* prc_sec_name = procedure->getSecurityName().nullStr();
|
|
|
|
// this request must have EXECUTE permission on the stored procedure
|
|
if (procedure->getName().package.isEmpty())
|
|
{
|
|
CMP_post_access(tdbb, csb, prc_sec_name, 0, SCL_execute, SCL_object_procedure,
|
|
procedure->getName().identifier.c_str());
|
|
}
|
|
else
|
|
{
|
|
CMP_post_access(tdbb, csb, prc_sec_name, 0, SCL_execute, SCL_object_package,
|
|
procedure->getName().package.c_str());
|
|
}
|
|
|
|
// Add the procedure to list of external objects accessed
|
|
ExternalAccess temp(ExternalAccess::exa_procedure, procedure->getId());
|
|
size_t idx;
|
|
if (!csb->csb_external.find(temp, idx))
|
|
csb->csb_external.insert(idx, temp);
|
|
}
|
|
|
|
|
|
RecordSource* CMP_post_rse(thread_db* tdbb, CompilerScratch* csb, RseNode* rse)
|
|
{
|
|
/**************************************
|
|
*
|
|
* C M P _ p o s t _ r s e
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Perform actual optimization of an RseNode and clear activity.
|
|
*
|
|
**************************************/
|
|
SET_TDBB(tdbb);
|
|
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(rse, type_nod);
|
|
|
|
RecordSource* rsb = OPT_compile(tdbb, csb, rse, NULL);
|
|
|
|
if (rse->flags & RseNode::FLAG_SINGULAR)
|
|
rsb = FB_NEW(*tdbb->getDefaultPool()) SingularStream(csb, rsb);
|
|
|
|
if (rse->flags & RseNode::FLAG_WRITELOCK)
|
|
{
|
|
for (USHORT i = 0; i < csb->csb_n_stream; i++)
|
|
csb->csb_rpt[i].csb_flags |= csb_update;
|
|
|
|
rsb = FB_NEW(*tdbb->getDefaultPool()) LockedStream(csb, rsb);
|
|
}
|
|
|
|
if (rse->flags & RseNode::FLAG_SCROLLABLE)
|
|
rsb = FB_NEW(*tdbb->getDefaultPool()) BufferedStream(csb, rsb);
|
|
|
|
// mark all the substreams as inactive
|
|
|
|
const NestConst<RecordSourceNode>* ptr = rse->rse_relations.begin();
|
|
for (const NestConst<RecordSourceNode>* const end = rse->rse_relations.end(); ptr != end; ++ptr)
|
|
{
|
|
const RecordSourceNode* const node = *ptr;
|
|
|
|
StreamsArray streams;
|
|
node->getStreams(streams);
|
|
|
|
for (StreamsArray::iterator i = streams.begin(); i != streams.end(); ++i)
|
|
csb->csb_rpt[*i].csb_flags &= ~csb_active;
|
|
}
|
|
|
|
return rsb;
|
|
}
|
|
|
|
|
|
static void post_trigger_access(CompilerScratch* csb,
|
|
jrd_rel* owner_relation,
|
|
ExternalAccess::exa_act operation, jrd_rel* view)
|
|
{
|
|
/**************************************
|
|
*
|
|
* p o s t _ t r i g g e r _ a c c e s s
|
|
*
|
|
**************************************
|
|
*
|
|
* Functional description
|
|
* Inherit access to triggers to be fired.
|
|
*
|
|
* When we detect that a trigger could be fired by a request,
|
|
* then we add the access list for that trigger to the access
|
|
* list for this request. That way, when we check access for
|
|
* the request we also check access for any other objects that
|
|
* could be fired off by the request.
|
|
*
|
|
* Note that when we add the access item, we specify that
|
|
* Trigger X needs access to resource Y.
|
|
* In the access list we parse here, if there is no "accessor"
|
|
* name then the trigger must access it directly. If there is
|
|
* an "accessor" name, then something accessed by this trigger
|
|
* must require the access.
|
|
*
|
|
* CVC: The third parameter is the owner of the triggers vector
|
|
* and was added to avoid triggers posting access checks to
|
|
* their base tables, since it's nonsense and causes weird
|
|
* messages about false REFERENCES right failures.
|
|
*
|
|
**************************************/
|
|
DEV_BLKCHK(csb, type_csb);
|
|
DEV_BLKCHK(view, type_rel);
|
|
|
|
// allow all access to internal requests
|
|
if (csb->csb_g_flags & (csb_internal | csb_ignore_perm))
|
|
return;
|
|
|
|
// Post trigger access
|
|
ExternalAccess temp(operation, owner_relation->rel_id, view ? view->rel_id : 0);
|
|
size_t i;
|
|
if (!csb->csb_external.find(temp, i))
|
|
csb->csb_external.insert(i, temp);
|
|
}
|