/*
 *	PROGRAM:	JRD Access Method
 *	MODULE:		met.epp
 *	DESCRIPTION:	Meta data handler
 *
 * 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.6.25 Claudio Valderrama: Finish MET_lookup_generator_id() by
 *	assigning it a number in the compiled requests table.
 *
 * 2001.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
 *                         conditionals, as the engine now fully supports
 *                         readonly databases.
 * 2001.10.03 Claudio Valderrama: MET_relation_owns_trigger() determines if
 *   there's a row in rdb$triggers with the given relation's and trigger's names.
 * 2001.10.04 Claudio Valderrama: MET_relation_default_class() determines if the
 *   given security class name is the default security class for a relation.
 *   Modify MET_lookup_field() so it can verify the field's security class, too.
 * 2002-02-24 Sean Leyne - Code Cleanup of old Win 3.1 port (WINDOWS_ONLY)
 * 2002-09-16 Nickolay Samofatov - Deferred trigger compilation changes
 * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
 * 2004.01.16 Vlad Horsun: added support for default parameters
 */

#include "firebird.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "../jrd/jrd.h"
#include "../jrd/val.h"
#include "../jrd/irq.h"
#include "../jrd/tra.h"
#include "../jrd/lck.h"
#include "../jrd/ods.h"
#include "../jrd/btr.h"
#include "../jrd/req.h"
#include "../jrd/exe.h"
#include "../jrd/scl.h"
#include "../jrd/blb.h"
#include "../jrd/met.h"
#include "../jrd/os/pio.h"
#include "../jrd/sdw.h"
#include "../jrd/flags.h"
#include "../jrd/lls.h"
#include "../jrd/intl.h"
#include "../jrd/align.h"
#include "../jrd/flu.h"
#include "../jrd/DataTypeUtil.h"
#include "../jrd/blob_filter.h"
#include "../dsql/StmtNodes.h"
#include "../intl/charsets.h"
#include "../common/gdsassert.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../common/dsc_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/evl_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/ext_proto.h"
#include "../jrd/flu_proto.h"
#include "../yvalve/gds_proto.h"
#include "../jrd/idx_proto.h"
#include "../jrd/ini_proto.h"

#include "../jrd/lck_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/mov_proto.h"
#include "../jrd/par_proto.h"
#include "../jrd/os/pio_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/sdw_proto.h"
#include "../common/utils_proto.h"

#include "../jrd/PreparedStatement.h"
#include "../jrd/RecordSourceNodes.h"
#include "../jrd/DebugInterface.h"
#include "../common/classes/MsgPrint.h"
#include "../jrd/Function.h"


#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif

// Pick up relation ids
#include "../jrd/ini.h"

DATABASE DB = FILENAME "ODS.RDB";

using namespace Jrd;
using namespace Firebird;

static int blocking_ast_dsql_cache(void* ast_object);
static DSqlCacheItem* get_dsql_cache_item(thread_db* tdbb, int type, const QualifiedName& name);
static int blocking_ast_procedure(void*);
static int blocking_ast_relation(void*);
static int partners_ast_relation(void*);
static int rescan_ast_relation(void*);
static ULONG get_rel_flags_from_FLAGS(USHORT);
static void get_trigger(thread_db*, jrd_rel*, bid*, bid*, TrigVector**, const TEXT*, FB_UINT64, bool,
	USHORT, const MetaName&, const string&, const bid*);
static bool get_type(thread_db*, USHORT*, const UCHAR*, const TEXT*);
static void lookup_view_contexts(thread_db*, jrd_rel*);
static void make_relation_scope_name(const TEXT*, const USHORT, string& str);
static ValueExprNode* parse_field_default_blr(thread_db* tdbb, bid* blob_id);
static BoolExprNode* parse_field_validation_blr(thread_db* tdbb, bid* blob_id, const MetaName name);
static bool resolve_charset_and_collation(thread_db*, USHORT*, const UCHAR*, const UCHAR*);
static void save_trigger_data(thread_db*, TrigVector**, jrd_rel*, JrdStatement*, blb*, blb*,
	const TEXT*, FB_UINT64, bool, USHORT, const MetaName&, const string&,
	const bid*);
static void scan_partners(thread_db*, jrd_rel*);
static void store_dependencies(thread_db*, CompilerScratch*, const jrd_rel*,
	const MetaName&, int, jrd_tra*);
static bool verify_TRG_ignore_perm(thread_db*, const MetaName&);


// Decompile all triggers from vector
static void release_cached_triggers(thread_db* tdbb, TrigVector* vector)
{
	if (!vector)
		return;

	for (FB_SIZE_T i = 0; i < vector->getCount(); i++)
		(*vector)[i].release(tdbb);
}


static void inc_int_use_count(JrdStatement* statement)
{
	// Handle sub-statements
	for (JrdStatement** subStatement = statement->subStatements.begin();
		 subStatement != statement->subStatements.end();
		 ++subStatement)
	{
		inc_int_use_count(*subStatement);
	}

	// Increment int_use_count for all procedures in resource list of request
	ResourceList& list = statement->resources;
	FB_SIZE_T i;

	for (list.find(Resource(Resource::rsc_procedure, 0, NULL, NULL, NULL), i);
		 i < list.getCount(); i++)
	{
		Resource& resource = list[i];
		if (resource.rsc_type != Resource::rsc_procedure)
			break;
		//// FIXME: CORE-4271: fb_assert(resource.rsc_routine->intUseCount >= 0);
		++resource.rsc_routine->intUseCount;
	}

	for (list.find(Resource(Resource::rsc_function, 0, NULL, NULL, NULL), i);
		 i < list.getCount(); i++)
	{
		Resource& resource = list[i];
		if (resource.rsc_type != Resource::rsc_function)
			break;
		//// FIXME: CORE-4271: fb_assert(resource.rsc_routine->intUseCount >= 0);
		++resource.rsc_routine->intUseCount;
	}
}


// Increment int_use_count for all procedures used by triggers
static void post_used_procedures(TrigVector* vector)
{
	if (!vector)
		return;

	for (FB_SIZE_T i = 0; i < vector->getCount(); i++)
	{
		JrdStatement* stmt = (*vector)[i].statement;
		if (stmt && !stmt->isActive())
			inc_int_use_count(stmt);
	}
}


void MET_get_domain(thread_db* tdbb, MemoryPool& csbPool, const MetaName& name, dsc* desc,
	FieldInfo* fieldInfo)
{
/**************************************
 *
 *	M E T _ g e t _ d o m a i n
 *
 **************************************
 *
 * Functional description
 *      Get domain descriptor and informations.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	bool found = false;

	AutoCacheRequest handle(tdbb, irq_l_domain, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle)
		FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ name.c_str()
	{
		if (DSC_make_descriptor(desc,
								FLD.RDB$FIELD_TYPE,
								FLD.RDB$FIELD_SCALE,
								FLD.RDB$FIELD_LENGTH,
								FLD.RDB$FIELD_SUB_TYPE,
								FLD.RDB$CHARACTER_SET_ID,
								FLD.RDB$COLLATION_ID))
		{
			found = true;

			// UNICODE_FSS_HACK
			if (FLD.RDB$SYSTEM_FLAG == 1)
				DataTypeUtilBase::adjustSysFieldLength(desc);

			if (fieldInfo)
			{
				fieldInfo->nullable = FLD.RDB$NULL_FLAG.NULL || FLD.RDB$NULL_FLAG == 0;

				Jrd::ContextPoolHolder context(tdbb, &csbPool);

				if (FLD.RDB$DEFAULT_VALUE.NULL)
					fieldInfo->defaultValue = NULL;
				else
					fieldInfo->defaultValue = parse_field_default_blr(tdbb, &FLD.RDB$DEFAULT_VALUE);

				if (FLD.RDB$VALIDATION_BLR.NULL)
					fieldInfo->validationExpr = NULL;
				else
				{
					fieldInfo->validationExpr = parse_field_validation_blr(tdbb,
						&FLD.RDB$VALIDATION_BLR, name);
				}
			}
		}
	}
	END_FOR

	if (!found)
	{
		ERR_post(Arg::Gds(isc_domnotdef) << Arg::Str(name));
	}
}


MetaName MET_get_relation_field(thread_db* tdbb, MemoryPool& csbPool, const MetaName& relationName,
	const MetaName& fieldName, dsc* desc, FieldInfo* fieldInfo)
{
/**************************************
 *
 *	M E T _ g e t _ r e l a t i o n _ f i e l d
 *
 **************************************
 *
 * Functional description
 *  Get relation field descriptor and informations.
 *  Returns field source name.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	bool found = false;
	MetaName sourceName;

	AutoCacheRequest handle(tdbb, irq_l_relfield, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle)
		RFL IN RDB$RELATION_FIELDS CROSS
		FLD IN RDB$FIELDS WITH
			RFL.RDB$RELATION_NAME EQ relationName.c_str() AND
			RFL.RDB$FIELD_NAME EQ fieldName.c_str() AND
			FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE
	{
		if (DSC_make_descriptor(desc,
								FLD.RDB$FIELD_TYPE,
								FLD.RDB$FIELD_SCALE,
								FLD.RDB$FIELD_LENGTH,
								FLD.RDB$FIELD_SUB_TYPE,
								FLD.RDB$CHARACTER_SET_ID,
								(RFL.RDB$COLLATION_ID.NULL ? FLD.RDB$COLLATION_ID : RFL.RDB$COLLATION_ID)))
		{
			found = true;

			// UNICODE_FSS_HACK
			if (FLD.RDB$SYSTEM_FLAG == 1)
				DataTypeUtilBase::adjustSysFieldLength(desc);

			sourceName = RFL.RDB$FIELD_SOURCE;

			if (fieldInfo)
			{
				fieldInfo->nullable = RFL.RDB$NULL_FLAG.NULL ?
					(FLD.RDB$NULL_FLAG.NULL || FLD.RDB$NULL_FLAG == 0) : RFL.RDB$NULL_FLAG == 0;

				Jrd::ContextPoolHolder context(tdbb, &csbPool);
				bid* defaultId = NULL;

				if (!RFL.RDB$DEFAULT_VALUE.NULL)
					defaultId = &RFL.RDB$DEFAULT_VALUE;
				else if (!FLD.RDB$DEFAULT_VALUE.NULL)
					defaultId = &FLD.RDB$DEFAULT_VALUE;

				if (defaultId)
					fieldInfo->defaultValue = parse_field_default_blr(tdbb, defaultId);
				else
					fieldInfo->defaultValue = NULL;

				if (FLD.RDB$VALIDATION_BLR.NULL)
					fieldInfo->validationExpr = NULL;
				else
				{
					fieldInfo->validationExpr = parse_field_validation_blr(tdbb,
						&FLD.RDB$VALIDATION_BLR, RFL.RDB$FIELD_SOURCE);
				}
			}
		}
	}
	END_FOR

	if (!found)
	{
		ERR_post(Arg::Gds(isc_dyn_column_does_not_exist) << Arg::Str(fieldName) <<
															Arg::Str(relationName));
	}

	return sourceName;
}


void MET_update_partners(thread_db* tdbb)
{
/**************************************
 *
 *      M E T _ u p d a t e _ p a r t n e r s
 *
 **************************************
 *
 * Functional description
 *      Mark all relations to update their links to FK partners
 *      Called when any index is deleted because engine don't know
 *      was it used in any FK or not
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* const attachment = tdbb->getAttachment();

	vec<jrd_rel*>* relations = attachment->att_relations;

	vec<jrd_rel*>::iterator ptr = relations->begin();
	for (const vec<jrd_rel*>::const_iterator end = relations->end(); ptr < end; ++ptr)
	{
		jrd_rel* relation = *ptr;
		if (!relation)
			continue;

		// signal other processes
		relation->rel_flags |= REL_check_partners;
		LCK_lock(tdbb, relation->rel_partners_lock, LCK_EX, LCK_WAIT);
		LCK_release(tdbb, relation->rel_partners_lock);
	}
}


static void adjust_dependencies(Routine* routine)
{
	if (routine->intUseCount == -1)
	{
		// Already processed
		return;
	}

	routine->intUseCount = -1; // Mark as undeletable

	if (routine->getStatement())
	{
		// Loop over procedures from resource list of request
		ResourceList& list = routine->getStatement()->resources;
		FB_SIZE_T i;

		for (list.find(Resource(Resource::rsc_procedure, 0, NULL, NULL, NULL), i);
			i < list.getCount(); i++)
		{
			Resource& resource = list[i];

			if (resource.rsc_type != Resource::rsc_procedure)
				break;

			routine = resource.rsc_routine;

			if (routine->intUseCount == routine->useCount)
			{
				// Mark it and all dependent procedures as undeletable
				adjust_dependencies(routine);
			}
		}

		for (list.find(Resource(Resource::rsc_function, 0, NULL, NULL, NULL), i);
			i < list.getCount(); i++)
		{
			Resource& resource = list[i];

			if (resource.rsc_type != Resource::rsc_function)
				break;

			routine = resource.rsc_routine;

			if (routine->intUseCount == routine->useCount)
			{
				// Mark it and all dependent functions as undeletable
				adjust_dependencies(routine);
			}
		}
	}
}


#ifdef DEV_BUILD
void MET_verify_cache(thread_db* tdbb)
{
/**************************************
 *
 *      M E T _ v e r i f y _ c a c h e
 *
 **************************************
 *
 * Functional description
 *      Check if all links between routines are properly counted
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* att = tdbb->getAttachment();
	if (!att)
		return;

	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		Routine* routine = *iter;

		if (routine && routine->getStatement() /*&&
			!(routine->flags & Routine::FLAG_OBSOLETE)*/ )
		{
			fb_assert(routine->intUseCount == 0);
		}
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Routine* routine = *iter;

		if (routine && routine->getStatement() /*&&
			!(routine->flags & Routine::FLAG_OBSOLETE)*/ )
		{
			fb_assert(routine->intUseCount == 0);
		}
	}

	// Walk procedures and calculate internal dependencies
	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		jrd_prc* routine = *iter;

		if (routine && routine->getStatement() /*&&
			!(routine->flags & Routine::FLAG_OBSOLETE)*/ )
		{
			inc_int_use_count(routine->getStatement());
		}
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Routine* routine = *iter;

		if (routine && routine->getStatement() /*&&
			!(routine->flags & Routine::FLAG_OBSOLETE)*/ )
		{
			inc_int_use_count(routine->getStatement());
		}
	}

	// Walk procedures again and check dependencies
	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		Routine* routine = *iter;

		if (routine && routine->getStatement() && /*
			 !(routine->flags & Routine::FLAG_OBSOLETE) && */
			 routine->useCount < routine->intUseCount)
		{
			char buffer[1024], *buf = buffer;
			buf += sprintf(buf, "Procedure %d:%s is not properly counted (use count=%d, prc use=%d). Used by: \n",
				routine->getId(), routine->getName().toString().c_str(),
				routine->useCount, routine->intUseCount);

			for (jrd_prc** iter2 = att->att_procedures.begin(); iter2 != att->att_procedures.end(); ++iter2)
			{
				Routine* routine2 = *iter2;

				if (routine2 && routine2->getStatement() /*&& !(routine2->flags & Routine::FLAG_OBSOLETE)*/ )
				{
					// Loop over procedures from resource list of request
					const ResourceList& list = routine2->getStatement()->resources;
					FB_SIZE_T i;

					for (list.find(Resource(Resource::rsc_procedure, 0, NULL, NULL, NULL), i);
					     i < list.getCount(); i++)
					{
						const Resource& resource = list[i];
						if (resource.rsc_type != Resource::rsc_procedure)
							break;

						if (resource.rsc_routine == routine)
						{
							// Do not enable this code in production builds unless
							// the possible B.O. is fixed here.
							buf += sprintf(buf, "%d:%s\n", routine2->getId(),
								routine2->getName().toString().c_str());
						}
					}
				}
			}

			for (Function** iter2 = att->att_functions.begin(); iter2 != att->att_functions.end(); ++iter2)
			{
				Routine* routine2 = *iter2;

				if (routine2 && routine2->getStatement() /*&& !(routine2->flags & Routine::FLAG_OBSOLETE)*/ )
				{
					// Loop over procedures from resource list of request
					const ResourceList& list = routine2->getStatement()->resources;
					FB_SIZE_T i;

					for (list.find(Resource(Resource::rsc_procedure, 0, NULL, NULL, NULL), i);
					     i < list.getCount(); i++)
					{
						const Resource& resource = list[i];
						if (resource.rsc_type != Resource::rsc_procedure)
							break;

						if (resource.rsc_routine == routine)
						{
							// Do not enable this code in production builds unless
							// the possible B.O. is fixed here.
							buf += sprintf(buf, "%d:%s\n", routine2->getId(),
								routine2->getName().toString().c_str());
						}
					}
				}
			}

			gds__log(buffer);
			fb_assert(false);
		}
	}

	// Walk functions again and check dependencies
	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Routine* routine = *iter;

		if (routine && routine->getStatement() && /*
			 !(routine->flags & Routine::FLAG_OBSOLETE) && */
			 routine->useCount < routine->intUseCount)
		{
			char buffer[1024], *buf = buffer;
			buf += sprintf(buf, "Function %d:%s is not properly counted (use count=%d, func use=%d). Used by: \n",
				routine->getId(), routine->getName().toString().c_str(),
				routine->useCount, routine->intUseCount);

			for (jrd_prc** iter2 = att->att_procedures.begin(); iter2 != att->att_procedures.end(); ++iter2)
			{
				Routine* routine2 = *iter2;

				if (routine2 && routine2->getStatement() /*&& !(routine2->flags & Routine::FLAG_OBSOLETE)*/ )
				{
					// Loop over procedures from resource list of request
					const ResourceList& list = routine2->getStatement()->resources;
					FB_SIZE_T i;

					for (list.find(Resource(Resource::rsc_function, 0, NULL, NULL, NULL), i);
					     i < list.getCount(); i++)
					{
						const Resource& resource = list[i];
						if (resource.rsc_type != Resource::rsc_function)
							break;

						if (resource.rsc_routine == routine)
						{
							// Do not enable this code in production builds unless
							// the possible B.O. is fixed here.
							buf += sprintf(buf, "%d:%s\n", routine2->getId(),
								routine2->getName().toString().c_str());
						}
					}
				}
			}

			for (Function** iter2 = att->att_functions.begin(); iter2 != att->att_functions.end(); ++iter2)
			{
				Routine* routine2 = *iter2;

				if (routine2 && routine2->getStatement() /*&& !(routine2->flags & Routine::FLAG_OBSOLETE)*/ )
				{
					// Loop over procedures from resource list of request
					const ResourceList& list = routine2->getStatement()->resources;
					FB_SIZE_T i;

					for (list.find(Resource(Resource::rsc_function, 0, NULL, NULL, NULL), i);
					     i < list.getCount(); i++)
					{
						const Resource& resource = list[i];
						if (resource.rsc_type != Resource::rsc_function)
							break;

						if (resource.rsc_routine == routine)
						{
							// Do not enable this code in production builds unless
							// the possible B.O. is fixed here.
							buf += sprintf(buf, "%d:%s\n", routine2->getId(),
								routine2->getName().toString().c_str());
						}
					}
				}
			}

			gds__log(buffer);
			fb_assert(false);
		}
	}

	// Fix back int_use_count
	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		Routine* routine = *iter;

		if (routine)
			routine->intUseCount = 0;
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Routine* routine = *iter;

		if (routine)
			routine->intUseCount = 0;
	}
}
#endif


void MET_clear_cache(thread_db* tdbb)
{
/**************************************
 *
 *      M E T _ c l e a r _ c a c h e
 *
 **************************************
 *
 * Functional description
 *      Remove all unused objects from metadata cache to
 *      release resources they use
 *
 **************************************/
	SET_TDBB(tdbb);
#ifdef DEV_BUILD
	MET_verify_cache(tdbb);
#endif

	Attachment* att = tdbb->getAttachment();

	for (unsigned i = 0; i < DB_TRIGGER_MAX; i++)
		release_cached_triggers(tdbb, att->att_triggers[i]);

	vec<jrd_rel*>* relations = att->att_relations;
	if (relations)
	{ // scope
		vec<jrd_rel*>::iterator ptr, end;
		for (ptr = relations->begin(), end = relations->end(); ptr < end; ++ptr)
		{
			jrd_rel* relation = *ptr;
			if (!relation)
				continue;
			release_cached_triggers(tdbb, relation->rel_pre_store);
			release_cached_triggers(tdbb, relation->rel_post_store);
			release_cached_triggers(tdbb, relation->rel_pre_erase);
			release_cached_triggers(tdbb, relation->rel_post_erase);
			release_cached_triggers(tdbb, relation->rel_pre_modify);
			release_cached_triggers(tdbb, relation->rel_post_modify);
		}
	} // scope

	// Walk routines and calculate internal dependencies.

	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		Routine* const routine = *iter;

		if (routine && routine->getStatement() &&
			!(routine->flags & Routine::FLAG_OBSOLETE) )
		{
			inc_int_use_count(routine->getStatement());
		}
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Function* const routine = *iter;

		if (routine && routine->getStatement() &&
			!(routine->flags & Routine::FLAG_OBSOLETE) )
		{
			inc_int_use_count(routine->getStatement());
		}
	}

	// Walk routines again and adjust dependencies for routines which will not be removed.

	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		Routine* const routine = *iter;

		if (routine && routine->getStatement() &&
			!(routine->flags & Routine::FLAG_OBSOLETE) &&
			routine->useCount != routine->intUseCount )
		{
			adjust_dependencies(routine);
		}
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Function* const routine = *iter;

		if (routine && routine->getStatement() &&
			!(routine->flags & Routine::FLAG_OBSOLETE) &&
			routine->useCount != routine->intUseCount )
		{
			adjust_dependencies(routine);
		}
	}

	// Deallocate all used requests.

	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		Routine* const routine = *iter;

		if (routine)
		{
			if (routine->getStatement() && !(routine->flags & Routine::FLAG_OBSOLETE) &&
				routine->intUseCount >= 0 &&
				routine->useCount == routine->intUseCount)
			{
				routine->releaseStatement(tdbb);

				if (routine->existenceLock)
					LCK_release(tdbb, routine->existenceLock);
				routine->existenceLock = NULL;
				routine->flags |= Routine::FLAG_OBSOLETE;
			}

			// Leave it in state 0 to avoid extra pass next time to clear it
			// Note: we need to adjust intUseCount for all routines
			// in cache because any of them may have been affected from
			// dependencies earlier. Even routines that were not scanned yet !
			routine->intUseCount = 0;
		}
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Function* const routine = *iter;

		if (routine)
		{
			if (routine->getStatement() && !(routine->flags & Routine::FLAG_OBSOLETE) &&
				routine->intUseCount >= 0 &&
				routine->useCount == routine->intUseCount)
			{
				routine->releaseStatement(tdbb);

				if (routine->existenceLock)
					LCK_release(tdbb, routine->existenceLock);
				routine->existenceLock = NULL;
				routine->flags |= Routine::FLAG_OBSOLETE;
			}

			// Leave it in state 0 to avoid extra pass next time to clear it
			// Note: we need to adjust intUseCount for all routines
			// in cache because any of them may have been affected from
			// dependencies earlier. Even routines that were not scanned yet !
			routine->intUseCount = 0;
		}
	}

#ifdef DEV_BUILD
	MET_verify_cache(tdbb);
#endif
}


bool MET_routine_in_use(thread_db* tdbb, Routine* routine)
{
/**************************************
 *
 *      M E T _ r o u t i n e _ i n _ u s e
 *
 **************************************
 *
 * Functional description
 *      Determine if routine is used by any user requests or transactions.
 *      Return false if routine is used only inside cache or not used at all.
 *
 **************************************/
	SET_TDBB(tdbb);

#ifdef DEV_BUILD
	MET_verify_cache(tdbb);
#endif

	Attachment* const att = tdbb->getAttachment();

	vec<jrd_rel*>* relations = att->att_relations;
	{ // scope
	    vec<jrd_rel*>::iterator ptr, end;

		for (ptr = relations->begin(), end = relations->end(); ptr < end; ++ptr)
		{
			jrd_rel* relation = *ptr;
			if (!relation) {
				continue;
			}
			post_used_procedures(relation->rel_pre_store);
			post_used_procedures(relation->rel_post_store);
			post_used_procedures(relation->rel_pre_erase);
			post_used_procedures(relation->rel_post_erase);
			post_used_procedures(relation->rel_pre_modify);
			post_used_procedures(relation->rel_post_modify);
		}
	} // scope

	// Walk routines and calculate internal dependencies

	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		jrd_prc* procedure = *iter;

		if (procedure && procedure->getStatement() &&
			!(procedure->flags & Routine::FLAG_OBSOLETE))
		{
			inc_int_use_count(procedure->getStatement());
		}
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Function* function = *iter;

		if (function && function->getStatement() &&
			!(function->flags & Routine::FLAG_OBSOLETE))
		{
			inc_int_use_count(function->getStatement());
		}
	}

	// Walk routines again and adjust dependencies for routines
	// which will not be removed.

	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		jrd_prc* procedure = *iter;

		if (procedure && procedure->getStatement() &&
			!(procedure->flags & Routine::FLAG_OBSOLETE) &&
			procedure->useCount != procedure->intUseCount && procedure != routine)
		{
			adjust_dependencies(procedure);
		}
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Function* function = *iter;

		if (function && function->getStatement() &&
			!(function->flags & Routine::FLAG_OBSOLETE) &&
			function->useCount != function->intUseCount && function != routine)
		{
			adjust_dependencies(function);
		}
	}

	const bool result = routine->useCount != routine->intUseCount;

	// Fix back intUseCount

	for (jrd_prc** iter = att->att_procedures.begin(); iter != att->att_procedures.end(); ++iter)
	{
		jrd_prc* procedure = *iter;

		if (procedure)
			procedure->intUseCount = 0;
	}

	for (Function** iter = att->att_functions.begin(); iter != att->att_functions.end(); ++iter)
	{
		Function* function = *iter;

		if (function)
			function->intUseCount = 0;
	}

#ifdef DEV_BUILD
	MET_verify_cache(tdbb);
#endif
	return result;
}


void MET_activate_shadow(thread_db* tdbb)
{
/**************************************
 *
 *      M E T _ a c t i v a t e _ s h a d o w
 *
 **************************************
 *
 * Functional description
 *      Activate the current database, which presumably
 *      was formerly a shadow, by deleting all records
 *      corresponding to the shadow that this database
 *      represents.
 *      Get rid of write ahead log for the activated shadow.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();

	// Erase any secondary files of the primary database of the shadow being activated.

	AutoRequest handle;

	FOR(REQUEST_HANDLE handle) X IN RDB$FILES
		WITH X.RDB$SHADOW_NUMBER NOT MISSING
			AND X.RDB$SHADOW_NUMBER EQ 0
		ERASE X;
	END_FOR

	PageSpace* pageSpace = dbb->dbb_page_manager.findPageSpace(DB_PAGE_SPACE);
	const char* dbb_file_name = pageSpace->file->fil_string;

	// go through files looking for any that expand to the current database name
	SCHAR expanded_name[MAXPATHLEN];
	AutoRequest handle2;
	handle.reset();

	FOR(REQUEST_HANDLE handle) X IN RDB$FILES
		WITH X.RDB$SHADOW_NUMBER NOT MISSING
			AND X.RDB$SHADOW_NUMBER NE 0

		PIO_expand(X.RDB$FILE_NAME, (USHORT)strlen(X.RDB$FILE_NAME),
					expanded_name, sizeof(expanded_name));

		if (!strcmp(expanded_name, dbb_file_name))
		{
			FOR(REQUEST_HANDLE handle2) Y IN RDB$FILES
				WITH X.RDB$SHADOW_NUMBER EQ Y.RDB$SHADOW_NUMBER
				MODIFY Y
					Y.RDB$SHADOW_NUMBER = 0;
				END_MODIFY
			END_FOR

			ERASE X;
		}
	END_FOR
}


ULONG MET_align(const dsc* desc, ULONG value)
{
/**************************************
 *
 *      M E T _ a l i g n
 *
 **************************************
 *
 * Functional description
 *      Align value (presumed offset) on appropriate border
 *      and return.
 *
 **************************************/
	USHORT alignment = desc->dsc_length;
	switch (desc->dsc_dtype)
	{
	case dtype_text:
	case dtype_cstring:
		return value;

	case dtype_varying:
		alignment = sizeof(USHORT);
		break;
	}

	alignment = MIN(alignment, FORMAT_ALIGNMENT);

	return FB_ALIGN(value, alignment);
}


DeferredWork* MET_change_fields(thread_db* tdbb, jrd_tra* transaction, const dsc* field_source)
{
/**************************************
 *
 *      M E T _ c h a n g e _ f i e l d s
 *
 **************************************
 *
 * Functional description
 *      Somebody is modifying RDB$FIELDS.
 *      Find all relations affected and schedule a format update.
 *      Find all procedures and triggers and schedule a BLR validate.
 *      Find all functions and schedule a BLR validate.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	dsc relation_name;
	DeferredWork* dw = NULL;
	AutoCacheRequest request(tdbb, irq_m_fields, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$RELATION_FIELDS WITH
			X.RDB$FIELD_SOURCE EQ field_source->dsc_address
	{
		relation_name.makeText(sizeof(X.RDB$RELATION_NAME), CS_METADATA,
			(UCHAR*) X.RDB$RELATION_NAME);
		SCL_check_relation(tdbb, &relation_name, SCL_alter);
		dw = DFW_post_work(transaction, dfw_update_format, &relation_name, 0);

		AutoCacheRequest request2(tdbb, irq_m_fields4, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request2)
			DEP IN RDB$DEPENDENCIES CROSS
			PRC IN RDB$PROCEDURES
				WITH DEP.RDB$DEPENDED_ON_NAME EQ X.RDB$RELATION_NAME AND
					 DEP.RDB$FIELD_NAME EQ X.RDB$FIELD_NAME AND
					 DEP.RDB$DEPENDED_ON_TYPE EQ obj_relation AND
					 DEP.RDB$DEPENDENT_TYPE EQ obj_procedure AND
					 DEP.RDB$DEPENDENT_NAME EQ PRC.RDB$PROCEDURE_NAME AND
					 PRC.RDB$PACKAGE_NAME MISSING
		{
			MetaName proc_name(PRC.RDB$PROCEDURE_NAME);

			dsc desc;
			desc.makeText(proc_name.length(), CS_METADATA, (UCHAR*) proc_name.c_str());

			DeferredWork* dw2 =
				DFW_post_work(transaction, dfw_modify_procedure, &desc, PRC.RDB$PROCEDURE_ID);
			DFW_post_work_arg(transaction, dw2, NULL, 0, dfw_arg_check_blr);
		}
		END_FOR

		request2.reset(tdbb, irq_m_fields5, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request2)
			DEP IN RDB$DEPENDENCIES CROSS
			TRG IN RDB$TRIGGERS
				WITH DEP.RDB$DEPENDED_ON_NAME EQ X.RDB$RELATION_NAME AND
					 DEP.RDB$FIELD_NAME EQ X.RDB$FIELD_NAME AND
					 DEP.RDB$DEPENDED_ON_TYPE EQ obj_relation AND
					 DEP.RDB$DEPENDENT_TYPE EQ obj_trigger AND
					 DEP.RDB$DEPENDENT_NAME EQ TRG.RDB$TRIGGER_NAME
		{
			MetaName trigger_name(TRG.RDB$TRIGGER_NAME);
			MetaName trigger_relation_name(TRG.RDB$RELATION_NAME);

			dsc desc;
			desc.makeText(trigger_name.length(), CS_METADATA, (UCHAR*) trigger_name.c_str());

			DeferredWork* dw2 = DFW_post_work(transaction, dfw_modify_trigger, &desc, 0);
			DFW_post_work_arg(transaction, dw2, NULL, TRG.RDB$TRIGGER_TYPE, dfw_arg_trg_type);

			desc.dsc_length = trigger_relation_name.length();
			desc.dsc_address = (UCHAR*) trigger_relation_name.c_str();
			DFW_post_work_arg(transaction, dw2, &desc, 0, dfw_arg_check_blr);
		}
		END_FOR

		request2.reset(tdbb, irq_m_fields8, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request2)
			DEP IN RDB$DEPENDENCIES CROSS
			FUN IN RDB$FUNCTIONS
				WITH DEP.RDB$DEPENDED_ON_NAME EQ X.RDB$RELATION_NAME AND
					 DEP.RDB$FIELD_NAME EQ X.RDB$FIELD_NAME AND
					 DEP.RDB$DEPENDED_ON_TYPE EQ obj_relation AND
					 DEP.RDB$DEPENDENT_TYPE EQ obj_udf AND
					 DEP.RDB$DEPENDENT_NAME EQ FUN.RDB$FUNCTION_NAME AND
					 FUN.RDB$PACKAGE_NAME MISSING
		{
			MetaName name(FUN.RDB$FUNCTION_NAME);

			dsc desc;
			desc.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());

			DeferredWork* dw2 =
				DFW_post_work(transaction, dfw_modify_function, &desc, FUN.RDB$FUNCTION_ID);
			DFW_post_work_arg(transaction, dw2, NULL, 0, dfw_arg_check_blr);
		}
		END_FOR
	}
	END_FOR

	request.reset(tdbb, irq_m_fields2, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		DEP IN RDB$DEPENDENCIES CROSS
		PRC IN RDB$PROCEDURES
			WITH DEP.RDB$DEPENDED_ON_NAME EQ field_source->dsc_address AND
				 DEP.RDB$DEPENDED_ON_TYPE EQ obj_field AND
				 DEP.RDB$DEPENDENT_TYPE EQ obj_procedure AND
				 DEP.RDB$DEPENDENT_NAME EQ PRC.RDB$PROCEDURE_NAME AND
				 PRC.RDB$PACKAGE_NAME MISSING
	{
		MetaName proc_name(PRC.RDB$PROCEDURE_NAME);

		dsc desc;
		desc.makeText(proc_name.length(), CS_METADATA, (UCHAR*) proc_name.c_str());

		DeferredWork* dw2 =
			DFW_post_work(transaction, dfw_modify_procedure, &desc, PRC.RDB$PROCEDURE_ID);
		DFW_post_work_arg(transaction, dw2, NULL, 0, dfw_arg_check_blr);
	}
	END_FOR

	request.reset(tdbb, irq_m_fields6, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		DEP IN RDB$DEPENDENCIES CROSS
		PRC IN RDB$PROCEDURES
			WITH DEP.RDB$DEPENDED_ON_NAME EQ field_source->dsc_address AND
				 DEP.RDB$DEPENDED_ON_TYPE EQ obj_field AND
				 (DEP.RDB$DEPENDENT_TYPE EQ obj_package_header OR
				 	DEP.RDB$DEPENDENT_TYPE EQ obj_package_body) AND
				 DEP.RDB$DEPENDENT_NAME EQ PRC.RDB$PACKAGE_NAME
	{
		MetaName proc_name(PRC.RDB$PROCEDURE_NAME);

		dsc desc;
		desc.makeText(proc_name.length(), CS_METADATA, (UCHAR*) proc_name.c_str());

		DeferredWork* dw2 = DFW_post_work(transaction, dfw_modify_procedure, &desc,
			PRC.RDB$PROCEDURE_ID, PRC.RDB$PACKAGE_NAME);
		DFW_post_work_arg(transaction, dw2, NULL, 0, dfw_arg_check_blr);
	}
	END_FOR

	request.reset(tdbb, irq_m_fields3, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		DEP IN RDB$DEPENDENCIES CROSS
		TRG IN RDB$TRIGGERS
			WITH DEP.RDB$DEPENDED_ON_NAME EQ field_source->dsc_address AND
				 DEP.RDB$DEPENDED_ON_TYPE EQ obj_field AND
				 DEP.RDB$DEPENDENT_TYPE EQ obj_trigger AND
				 DEP.RDB$DEPENDENT_NAME EQ TRG.RDB$TRIGGER_NAME
	{
		MetaName trigger_name(TRG.RDB$TRIGGER_NAME);
		MetaName trigger_relation_name(TRG.RDB$RELATION_NAME);

		dsc desc;
		desc.makeText(trigger_name.length(), CS_METADATA, (UCHAR*) trigger_name.c_str());

		DeferredWork* dw2 = DFW_post_work(transaction, dfw_modify_trigger, &desc, 0);
		DFW_post_work_arg(transaction, dw2, NULL, TRG.RDB$TRIGGER_TYPE, dfw_arg_trg_type);

		desc.dsc_length = trigger_relation_name.length();
		desc.dsc_address = (UCHAR*) trigger_relation_name.c_str();
		DFW_post_work_arg(transaction, dw2, &desc, 0, dfw_arg_check_blr);
	}
	END_FOR

	request.reset(tdbb, irq_m_fields7, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		DEP IN RDB$DEPENDENCIES CROSS
		FUN IN RDB$FUNCTIONS
			WITH DEP.RDB$DEPENDED_ON_NAME EQ field_source->dsc_address AND
				 DEP.RDB$DEPENDED_ON_TYPE EQ obj_field AND
				 DEP.RDB$DEPENDENT_TYPE EQ obj_udf AND
				 DEP.RDB$DEPENDENT_NAME EQ FUN.RDB$FUNCTION_NAME AND
				 FUN.RDB$PACKAGE_NAME MISSING
	{
		MetaName name(FUN.RDB$FUNCTION_NAME);

		dsc desc;
		desc.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());

		DeferredWork* dw2 =
			DFW_post_work(transaction, dfw_modify_function, &desc, FUN.RDB$FUNCTION_ID);
		DFW_post_work_arg(transaction, dw2, NULL, 0, dfw_arg_check_blr);
	}
	END_FOR

	request.reset(tdbb, irq_m_fields9, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		DEP IN RDB$DEPENDENCIES CROSS
		FUN IN RDB$FUNCTIONS
			WITH DEP.RDB$DEPENDED_ON_NAME EQ field_source->dsc_address AND
				 DEP.RDB$DEPENDED_ON_TYPE EQ obj_field AND
				 (DEP.RDB$DEPENDENT_TYPE EQ obj_package_header OR
				 	DEP.RDB$DEPENDENT_TYPE EQ obj_package_body) AND
				 DEP.RDB$DEPENDENT_NAME EQ FUN.RDB$PACKAGE_NAME
	{
		MetaName name(FUN.RDB$FUNCTION_NAME);

		dsc desc;
		desc.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());

		DeferredWork* dw2 = DFW_post_work(transaction, dfw_modify_function, &desc,
			FUN.RDB$FUNCTION_ID, FUN.RDB$PACKAGE_NAME);
		DFW_post_work_arg(transaction, dw2, NULL, 0, dfw_arg_check_blr);
	}
	END_FOR

	return dw;
}


Format* MET_current(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
 *
 *      M E T _ c u r r e n t
 *
 **************************************
 *
 * Functional description
 *      Get the current format for a relation.  The current format is the
 *      format in which new records are to be stored.
 *
 **************************************/

	if (relation->rel_current_format)
		return relation->rel_current_format;

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	if (!(relation->rel_flags & REL_scanned))
	{
		AutoCacheRequest request(tdbb, irq_l_curr_format, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request)
			REL IN RDB$RELATIONS
			WITH REL.RDB$RELATION_ID EQ relation->rel_id
		{
			relation->rel_current_fmt = REL.RDB$FORMAT;
		}
		END_FOR
	}

	// Usually, format numbers start with one and they are present in RDB$FORMATS.
	// However, system tables have zero as their initial format and they don't have
	// any related records in RDB$FORMATS, instead their rel_formats[0] is initialized
	// directly (see ini.epp). Every other case of zero format number found for an already
	// scanned table must be catched here and investigated.
	fb_assert(relation->rel_current_fmt || relation->isSystem());

	relation->rel_current_format = MET_format(tdbb, relation, relation->rel_current_fmt);

	return relation->rel_current_format;
}


void MET_delete_dependencies(thread_db* tdbb,
							 const MetaName& object_name,
							 int dependency_type,
							 jrd_tra* transaction)
{
/**************************************
 *
 *      M E T _ d e l e t e _ d e p e n d e n c i e s
 *
 **************************************
 *
 * Functional description
 *      Delete all dependencies for the specified
 *      object of given type.
 *
 **************************************/
	SET_TDBB(tdbb);

//	if (!object_name)
//		return;
//
	AutoCacheRequest request(tdbb, irq_d_deps, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		DEP IN RDB$DEPENDENCIES
			WITH DEP.RDB$DEPENDENT_NAME = object_name.c_str()
			AND DEP.RDB$DEPENDENT_TYPE = dependency_type
	{
		ERASE DEP;
	}
	END_FOR
}


void MET_delete_shadow(thread_db* tdbb, USHORT shadow_number)
{
/**************************************
 *
 *      M E T _ d e l e t e _ s h a d o w
 *
 **************************************
 *
 * Functional description
 *      When any of the shadows in RDB$FILES for a particular
 *      shadow are deleted, stop shadowing to that file and
 *      remove all other files from the same shadow.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();

	AutoRequest handle;

	FOR(REQUEST_HANDLE handle)
		X IN RDB$FILES WITH X.RDB$SHADOW_NUMBER EQ shadow_number
		ERASE X;
	END_FOR

	for (Shadow* shadow = dbb->dbb_shadow; shadow; shadow = shadow->sdw_next)
	{
		if (shadow->sdw_number == shadow_number) {
			shadow->sdw_flags |= SDW_shutdown;
		}
	}

	// notify other processes to check for shadow deletion
	if (SDW_lck_update(tdbb, 0))
		SDW_notify(tdbb);
}


bool MET_dsql_cache_use(thread_db* tdbb, int type, const MetaName& name, const MetaName& package)
{
	DSqlCacheItem* item = get_dsql_cache_item(tdbb, type, QualifiedName(name, package));

	const bool obsolete = item->obsolete;

	if (!item->locked)
	{
		// lock to be notified by others when we should mark as obsolete
		LCK_lock(tdbb, item->lock, LCK_SR, LCK_WAIT);
		item->locked = true;
	}

	item->obsolete = false;

	return obsolete;
}


void MET_dsql_cache_release(thread_db* tdbb, int type, const MetaName& name, const MetaName& package)
{
	DSqlCacheItem* item = get_dsql_cache_item(tdbb, type, QualifiedName(name, package));

	// release lock
	LCK_release(tdbb, item->lock);

	// notify others through AST to mark as obsolete
	const USHORT key_length = item->lock->lck_length;
	AutoPtr<Lock> temp_lock(FB_NEW_RPT(*tdbb->getDefaultPool(), key_length)
		Lock(tdbb, key_length, LCK_dsql_cache));
	memcpy(temp_lock->getKeyString(), item->lock->getKeyString(), key_length);

	if (LCK_lock(tdbb, temp_lock, LCK_EX, LCK_WAIT))
		LCK_release(tdbb, temp_lock);

	item->locked = false;
	item->obsolete = false;
}


void MET_error(const TEXT* string, ...)
{
/**************************************
 *
 *      M E T _ e r r o r
 *
 **************************************
 *
 * Functional description
 *      Post an error in a metadata update
 *      Oh, wow.
 *
 **************************************/
	TEXT s[128];
	va_list ptr;

	va_start(ptr, string);
	VSNPRINTF(s, sizeof(s), string, ptr);
	va_end(ptr);

	ERR_post(Arg::Gds(isc_no_meta_update) <<
			 Arg::Gds(isc_random) << Arg::Str(s));
}

Format* MET_format(thread_db* tdbb, jrd_rel* relation, USHORT number)
{
/**************************************
 *
 *      M E T _ f o r m a t
 *
 **************************************
 *
 * Functional description
 *      Lookup a format for given relation.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();

	Format* format;
	vec<Format*>* formats = relation->rel_formats;
	if (formats && (number < formats->count()) && (format = (*formats)[number]))
	{
		return format;
	}

	format = NULL;
	AutoCacheRequest request(tdbb, irq_r_format, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$FORMATS WITH X.RDB$RELATION_ID EQ relation->rel_id AND
			X.RDB$FORMAT EQ number
	{
		blb* blob = blb::open(tdbb, attachment->getSysTransaction(), &X.RDB$DESCRIPTOR);

		// Use generic representation of formats with 32-bit offsets

		HalfStaticArray<UCHAR, BUFFER_MEDIUM> buffer;
		blob->BLB_get_data(tdbb, buffer.getBuffer(blob->blb_length), blob->blb_length);
		unsigned bufferPos = 2;
		USHORT count = buffer[0] | (buffer[1] << 8);

		format = Format::newFormat(*relation->rel_pool, count);

		Array<Ods::Descriptor> odsDescs;
		Ods::Descriptor* odsDesc = odsDescs.getBuffer(count);
		memcpy(odsDesc, buffer.begin() + bufferPos, count * sizeof(Ods::Descriptor));

		for (Format::fmt_desc_iterator desc = format->fmt_desc.begin();
			 desc < format->fmt_desc.end(); ++desc, ++odsDesc)
		{
			*desc = *odsDesc;
			if (odsDesc->dsc_offset)
				format->fmt_length = odsDesc->dsc_offset + desc->dsc_length;
		}

		const UCHAR* p = buffer.begin() + bufferPos + count * sizeof(Ods::Descriptor);
		count = p[0] | (p[1] << 8);
		p += 2;

		Array<UCHAR> tmpArray;	// must be aligned for the maximum datatype align requirement
		while (count-- > 0)
		{
			USHORT offset = p[0] | (p[1] << 8);
			p += 2;

			Ods::Descriptor odsDflDesc;
			memcpy(&odsDflDesc, p, sizeof(odsDflDesc));
			p += sizeof(Ods::Descriptor);

			dsc desc = odsDflDesc;

			desc.dsc_address = tmpArray.getBuffer(desc.dsc_length, false);
			memcpy(desc.dsc_address, p, desc.dsc_length);
			EVL_make_value(tdbb, &desc, &format->fmt_defaults[offset], relation->rel_pool);

			p += desc.dsc_length;
		}
	}
	END_FOR

	if (!format)
		format = Format::newFormat(*relation->rel_pool);

	format->fmt_version = number;

	// Link the format block into the world

	formats = relation->rel_formats =
		vec<Format*>::newVector(*relation->rel_pool, relation->rel_formats, number + 1);
	(*formats)[number] = format;

	return format;
}


bool MET_get_char_coll_subtype(thread_db* tdbb, USHORT* id, const UCHAR* name, USHORT length)
{
/**************************************
 *
 *      M E T _ g e t _ c h a r _ c o l l _ s u b t y p e
 *
 **************************************
 *
 * Functional description
 *      Character types can be specified as either:
 *         a) A POSIX style locale name "<collation>.<characterset>"
 *         or
 *         b) A simple <characterset> name (using default collation)
 *         c) A simple <collation> name    (use charset for collation)
 *
 *      Given an ASCII7 string which could be any of the above, try to
 *      resolve the name in the order a, b, c
 *      a) is only tried iff the name contains a period.
 *      (in which case b) and c) are not tried).
 *
 * Return:
 *      1 if no errors (and *id is set).
 *      0 if the name could not be resolved.
 *
 **************************************/
	SET_TDBB(tdbb);

	fb_assert(id != NULL);
	fb_assert(name != NULL);

	const UCHAR* const end_name = name + length;

	// Force key to uppercase, following C locale rules for uppercasing
	// At the same time, search for the first period in the string (if any)
	UCHAR buffer[MAX_SQL_IDENTIFIER_SIZE];			// BASED ON RDB$COLLATION_NAME
	UCHAR* p = buffer;
	UCHAR* period = NULL;
	for (; name < end_name && p < buffer + sizeof(buffer) - 1; p++, name++)
	{
		*p = UPPER7(*name);
		if ((*p == '.') && !period) {
			period = p;
		}
	}
	*p = 0;

	// Is there a period, separating collation name from character set?
	if (period)
	{
		*period = 0;
		return resolve_charset_and_collation(tdbb, id, period + 1, buffer);
	}

	// Is it a character set name (implying charset default collation sequence)

	bool res = resolve_charset_and_collation(tdbb, id, buffer, NULL);
	if (!res)
	{
		// Is it a collation name (implying implementation-default character set)
		res = resolve_charset_and_collation(tdbb, id, NULL, buffer);
	}
	return res;
}


bool MET_get_char_coll_subtype_info(thread_db* tdbb, USHORT id, SubtypeInfo* info)
{
/**************************************
 *
 *      M E T _ g e t _ c h a r _ c o l l _ s u b t y p e _ i n f o
 *
 **************************************
 *
 * Functional description
 *      Get charset and collation informations
 *      for a subtype ID.
 *
 **************************************/
	fb_assert(info != NULL);

	const USHORT charset_id = id & 0x00FF;
	const USHORT collation_id = id >> 8;

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	AutoCacheRequest request(tdbb, irq_l_subtype, IRQ_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request) FIRST 1
		CL IN RDB$COLLATIONS CROSS
		CS IN RDB$CHARACTER_SETS
		WITH CL.RDB$CHARACTER_SET_ID EQ charset_id AND
			CL.RDB$COLLATION_ID EQ collation_id AND
			CS.RDB$CHARACTER_SET_ID EQ CL.RDB$CHARACTER_SET_ID
	{
		found = true;

		info->charsetName = CS.RDB$CHARACTER_SET_NAME;
		info->collationName = CL.RDB$COLLATION_NAME;

		if (CL.RDB$BASE_COLLATION_NAME.NULL)
			info->baseCollationName = info->collationName;
		else
			info->baseCollationName = CL.RDB$BASE_COLLATION_NAME;

		if (CL.RDB$SPECIFIC_ATTRIBUTES.NULL)
			info->specificAttributes.clear();
		else
		{
			blb* blob = blb::open(tdbb, attachment->getSysTransaction(), &CL.RDB$SPECIFIC_ATTRIBUTES);
			const ULONG length = blob->blb_length;

			// ASF: Here info->specificAttributes is in UNICODE_FSS charset.
			// It will be converted to the collation charset in intl.cpp
			blob->BLB_get_data(tdbb, info->specificAttributes.getBuffer(length), length);
		}

		info->attributes = (USHORT)CL.RDB$COLLATION_ATTRIBUTES;
		info->ignoreAttributes = CL.RDB$COLLATION_ATTRIBUTES.NULL;
	}
	END_FOR

	return found;
}


DmlNode* MET_get_dependencies(thread_db* tdbb,
							  jrd_rel* relation,
							  const UCHAR* blob,
							  const ULONG blob_length,
							  CompilerScratch* view_csb,
							  bid* blob_id,
							  JrdStatement** statementPtr,
							  CompilerScratch** csb_ptr,
							  const MetaName& object_name,
							  int type,
							  USHORT flags,
							  jrd_tra* transaction,
							  const MetaName& domain_validation)
{
/**************************************
 *
 *      M E T _ g e t _ d e p e n d e n c i e s
 *
 **************************************
 *
 * Functional description
 *      Get dependencies for an object by parsing
 *      the blr used in its definition.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	fb_assert(domain_validation.isEmpty() || object_name == domain_validation); // for now

	CompilerScratch* csb = CompilerScratch::newCsb(*tdbb->getDefaultPool(), 5, domain_validation);
	csb->csb_g_flags |= (csb_get_dependencies | flags);

	DmlNode* node;

	if (blob)
	{
		node = PAR_blr(tdbb, relation, blob, blob_length, view_csb, &csb, statementPtr,
			(type == obj_trigger && relation != NULL), 0);
	}
	else
	{
		node = MET_parse_blob(tdbb, relation, blob_id, &csb, statementPtr,
			(type == obj_trigger && relation != NULL), type == obj_validation);
	}

	if (type == obj_computed)
	{
		MetaName domainName;

		AutoRequest handle;

		FOR(REQUEST_HANDLE handle)
			RLF IN RDB$RELATION_FIELDS CROSS
				FLD IN RDB$FIELDS WITH
				RLF.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND
				RLF.RDB$RELATION_NAME EQ relation->rel_name.c_str() AND
				RLF.RDB$FIELD_NAME EQ object_name.c_str()
		{
			domainName = FLD.RDB$FIELD_NAME;
		}
		END_FOR

		if (type != obj_package_body)
			MET_delete_dependencies(tdbb, domainName, type, transaction);

		store_dependencies(tdbb, csb, relation, domainName, type, transaction);
	}
	else
	{
		if (type != obj_package_body)
			MET_delete_dependencies(tdbb, object_name, type, transaction);

		store_dependencies(tdbb, csb, relation, object_name, type, transaction);
	}

	if (csb_ptr)
		*csb_ptr = csb;
	else
		delete csb;

	return node;
}


jrd_fld* MET_get_field(const jrd_rel* relation, USHORT id)
{
/**************************************
 *
 *      M E T _ g e t _ f i e l d
 *
 **************************************
 *
 * Functional description
 *      Get the field block for a field if possible.  If not,
 *      return NULL;
 *
 **************************************/
	vec<jrd_fld*>* vector;

	if (!relation || !(vector = relation->rel_fields) || id >= vector->count())
		return NULL;

	return (*vector)[id];
}


void MET_get_shadow_files(thread_db* tdbb, bool delete_files)
{
/**************************************
 *
 *      M E T _ g e t _ s h a d o w _ f i l e s
 *
 **************************************
 *
 * Functional description
 *      Check the shadows found in the database against
 *      our in-memory list: if any new shadow files have
 *      been defined since the last time we looked, start
 *      shadowing to them; if any have been deleted, stop
 *      shadowing to them.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();

	AutoRequest handle;

	FOR(REQUEST_HANDLE handle) X IN RDB$FILES
		WITH X.RDB$SHADOW_NUMBER NOT MISSING
			AND X.RDB$SHADOW_NUMBER NE 0
			AND X.RDB$FILE_SEQUENCE EQ 0
	{
		if ((X.RDB$FILE_FLAGS & FILE_shadow) && !(X.RDB$FILE_FLAGS & FILE_inactive))
		{
			const USHORT file_flags = X.RDB$FILE_FLAGS;
			SDW_start(tdbb, X.RDB$FILE_NAME, X.RDB$SHADOW_NUMBER, file_flags, delete_files);

			// if the shadow exists, mark the appropriate shadow
			// block as found for the purposes of this routine;
			// if the shadow was conditional and is no longer, note it

			for (Shadow* shadow = dbb->dbb_shadow; shadow; shadow = shadow->sdw_next)
			{
				if ((shadow->sdw_number == X.RDB$SHADOW_NUMBER) && !(shadow->sdw_flags & SDW_IGNORE))
				{
					shadow->sdw_flags |= SDW_found;
					if (!(file_flags & FILE_conditional)) {
						shadow->sdw_flags &= ~SDW_conditional;
					}
					break;
				}
			}
		}
	}
	END_FOR

	// if any current shadows were not defined in database, mark
	// them to be shutdown since they don't exist anymore

	for (Shadow* shadow = dbb->dbb_shadow; shadow; shadow = shadow->sdw_next)
	{
		if (!(shadow->sdw_flags & SDW_found))
			shadow->sdw_flags |= SDW_shutdown;
		else
			shadow->sdw_flags &= ~SDW_found;
	}

	SDW_check(tdbb);
}


void MET_load_db_triggers(thread_db* tdbb, int type)
{
/**************************************
 *
 *      M E T _ l o a d _ d b _ t r i g g e r s
 *
 **************************************
 *
 * Functional description
 *      Load database triggers from RDB$TRIGGERS.
 *
 **************************************/

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();
	CHECK_DBB(dbb);

	if ((attachment->att_flags & ATT_no_db_triggers) ||
		attachment->att_triggers[type] != NULL)
	{
		return;
	}

	attachment->att_triggers[type] = FB_NEW_POOL(*attachment->att_pool)
		TrigVector(*attachment->att_pool);
	attachment->att_triggers[type]->addRef();

	AutoRequest trigger_request;
	int encoded_type = type | TRIGGER_TYPE_DB;

	FOR(REQUEST_HANDLE trigger_request)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$RELATION_NAME MISSING AND
			 TRG.RDB$TRIGGER_TYPE EQ encoded_type AND
			 TRG.RDB$TRIGGER_INACTIVE EQ 0
		SORTED BY TRG.RDB$TRIGGER_SEQUENCE
	{
		MET_load_trigger(tdbb, NULL, TRG.RDB$TRIGGER_NAME, &attachment->att_triggers[type]);
	}
	END_FOR
}


// Load DDL triggers from RDB$TRIGGERS.
void MET_load_ddl_triggers(thread_db* tdbb)
{
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();
	CHECK_DBB(dbb);

	if ((attachment->att_flags & ATT_no_db_triggers) ||
		attachment->att_ddl_triggers != NULL)
	{
		return;
	}

	attachment->att_ddl_triggers = FB_NEW_POOL(*attachment->att_pool)
		TrigVector(*attachment->att_pool);
	attachment->att_ddl_triggers->addRef();

	AutoRequest trigger_request;

	FOR(REQUEST_HANDLE trigger_request)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$RELATION_NAME MISSING AND
			 TRG.RDB$TRIGGER_INACTIVE EQ 0
		SORTED BY TRG.RDB$TRIGGER_SEQUENCE
	{
		if ((TRG.RDB$TRIGGER_TYPE & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DDL)
		{
			MET_load_trigger(tdbb, NULL, TRG.RDB$TRIGGER_NAME,
				&attachment->att_ddl_triggers);
		}
	}
	END_FOR
}


void MET_load_trigger(thread_db* tdbb,
					  jrd_rel* relation,
					  const MetaName& trigger_name,
					  TrigVector** triggers)
{
/**************************************
 *
 *      M E T _ l o a d _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *      Load triggers from RDB$TRIGGERS.  If a requested,
 *      also load triggers from RDB$RELATIONS.
 *
 **************************************/
	TEXT errmsg[MAX_ERRMSG_LEN + 1];

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();
	CHECK_DBB(dbb);

	if (relation)
	{
		if (relation->rel_flags & REL_sys_trigs_being_loaded)
			return;

		// No need to load table triggers for ReadOnly databases,
		// since INSERT/DELETE/UPDATE statements are not going to be allowed
		// hvlad: GTT with ON COMMIT DELETE ROWS clause is writable

		if (dbb->readOnly() && !(relation->rel_flags & REL_temp_tran))
			return;
	}

	// Scan RDB$TRIGGERS next

	AutoCacheRequest request(tdbb, irq_s_triggers, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$TRIGGER_NAME EQ trigger_name.c_str() AND
			 (TRG.RDB$TRIGGER_INACTIVE MISSING OR TRG.RDB$TRIGGER_INACTIVE EQ 0)
	{
		// check if the trigger is to be fired without any permissions
		// checks. Verify such a claim
		USHORT trig_flags = (USHORT) TRG.RDB$FLAGS;

		// if there is an ignore permission flag, see if it is legit
		if ((TRG.RDB$FLAGS & TRG_ignore_perm) && !verify_TRG_ignore_perm(tdbb, trigger_name))
		{
			fb_msg_format(NULL, JRD_BUGCHK, 304, sizeof(errmsg),
							errmsg, MsgFormat::SafeArg() << trigger_name.c_str());
			ERR_log(JRD_BUGCHK, 304, errmsg);

			trig_flags &= ~TRG_ignore_perm;
		}

		bid debug_blob_id;
		debug_blob_id.clear();

		bid extBodyId;
		extBodyId.clear();

		if (!TRG.RDB$DEBUG_INFO.NULL)	// ODS_11_1
			debug_blob_id = TRG.RDB$DEBUG_INFO;

		MetaName engine;
		string entryPoint;

		if (!TRG.RDB$ENGINE_NAME.NULL)	// ODS_12_0
		{
			engine = TRG.RDB$ENGINE_NAME;
			extBodyId = TRG.RDB$TRIGGER_SOURCE;
		}

		if (!TRG.RDB$ENTRYPOINT.NULL)	// ODS_12_0
			entryPoint = TRG.RDB$ENTRYPOINT;

		if (TRG.RDB$RELATION_NAME.NULL)
		{
			if ((TRG.RDB$TRIGGER_TYPE & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB ||
				(TRG.RDB$TRIGGER_TYPE & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DDL)
			{
				// this is a database trigger
				get_trigger(tdbb,
							relation,
							&TRG.RDB$TRIGGER_BLR,
							&debug_blob_id,
							triggers,
							TRG.RDB$TRIGGER_NAME,
							TRG.RDB$TRIGGER_TYPE & ~TRIGGER_TYPE_MASK,
							(bool) TRG.RDB$SYSTEM_FLAG,
							trig_flags,
							engine,
							entryPoint,
							&extBodyId);
			}
		}
		else
		{
			// dimitr: support for the universal triggers
			int trigger_action, slot_index = 0;
			while ((trigger_action = TRIGGER_ACTION_SLOT(TRG.RDB$TRIGGER_TYPE, ++slot_index)) > 0)
			{
				get_trigger(tdbb,
							relation,
							&TRG.RDB$TRIGGER_BLR,
							&debug_blob_id,
							triggers + trigger_action,
							TRG.RDB$TRIGGER_NAME,
							(UCHAR) trigger_action,
							(bool) TRG.RDB$SYSTEM_FLAG,
							trig_flags,
							engine,
							entryPoint,
							&extBodyId);
			}
		}
	}
	END_FOR
}



void MET_lookup_cnstrt_for_index(thread_db* tdbb,
								 MetaName& constraint_name,
								 const MetaName& index_name)
{
/**************************************
 *
 *      M E T _ l o o k u p _ c n s t r t _ f o r _ i n d e x
 *
 **************************************
 *
 * Functional description
 *      Lookup  constraint name from index name, if one exists.
 *      Calling routine must pass a buffer of at least 32 bytes.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	constraint_name = "";
	AutoCacheRequest request(tdbb, irq_l_cnstrt, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$RELATION_CONSTRAINTS WITH X.RDB$INDEX_NAME EQ index_name.c_str()
	{
		constraint_name = X.RDB$CONSTRAINT_NAME;
	}
	END_FOR
}


void MET_lookup_cnstrt_for_trigger(thread_db* tdbb,
								   MetaName& constraint_name,
								   MetaName& relation_name,
								   const MetaName& trigger_name)
{
/**************************************
 *
 *      M E T _ l o o k u p _ c n s t r t _ f o r _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *      Lookup  constraint name from trigger name, if one exists.
 *      Calling routine must pass a buffer of at least 32 bytes.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	constraint_name = "";
	relation_name = "";
	AutoCacheRequest request(tdbb, irq_l_check, IRQ_REQUESTS);
	AutoCacheRequest request2(tdbb, irq_l_check2, IRQ_REQUESTS);

	// utilize two requests rather than one so that we
	// guarantee we always return the name of the relation
	// that the trigger is defined on, even if we don't
	// have a check constraint defined for that trigger

	FOR(REQUEST_HANDLE request)
		Y IN RDB$TRIGGERS WITH
			Y.RDB$TRIGGER_NAME EQ trigger_name.c_str()
	{
		FOR(REQUEST_HANDLE request2)
			X IN RDB$CHECK_CONSTRAINTS WITH
				X.RDB$TRIGGER_NAME EQ Y.RDB$TRIGGER_NAME
		{
			constraint_name = X.RDB$CONSTRAINT_NAME;
		}
		END_FOR

		relation_name = Y.RDB$RELATION_NAME;
	}
	END_FOR
}


void MET_lookup_exception(thread_db* tdbb,
						  SLONG number,
						  MetaName& name,
						  string* message)
{
/**************************************
 *
 *      M E T _ l o o k u p _ e x c e p t i o n
 *
 **************************************
 *
 * Functional description
 *      Lookup exception by number and return its name and message.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// We need to look up exception in RDB$EXCEPTIONS

	AutoCacheRequest request(tdbb, irq_l_exception, IRQ_REQUESTS);

	name = "";
	if (message)
		*message = "";

	FOR(REQUEST_HANDLE request)
		X IN RDB$EXCEPTIONS WITH X.RDB$EXCEPTION_NUMBER = number
	{
		if (!X.RDB$EXCEPTION_NAME.NULL)
			name = X.RDB$EXCEPTION_NAME;

		if (!X.RDB$MESSAGE.NULL && message)
			*message = X.RDB$MESSAGE;
	}
	END_FOR
}


bool MET_load_exception(thread_db* tdbb, ExceptionItem& item)
{
/**************************************
 *
 *      M E T _ l o a d _ e x c e p t i o n
 *
 **************************************
 *
 * Functional description
 *      Lookup exception by name and fill the passed instance.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// We need to look up exception in RDB$EXCEPTIONS

	AutoCacheRequest request(tdbb, irq_l_except_no, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$EXCEPTIONS WITH X.RDB$EXCEPTION_NAME = item.name.c_str()
	{
		item.type = ExceptionItem::XCP_CODE;
		item.code = X.RDB$EXCEPTION_NUMBER;
		item.secName = X.RDB$SECURITY_CLASS;

		return true;
	}
	END_FOR

	return false;
}


int MET_lookup_field(thread_db* tdbb, jrd_rel* relation, const MetaName& name)
{
/**************************************
 *
 *      M E T _ l o o k u p _ f i e l d
 *
 **************************************
 *
 * Functional description
 *      Look up a field name.
 *
 *	if the field is not found return -1
 *
 *****************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// Start by checking field names that we already know
	vec<jrd_fld*>* vector = relation->rel_fields;

	if (vector)
	{
		int id = 0;
		vec<jrd_fld*>::iterator fieldIter = vector->begin();

		for (const vec<jrd_fld*>::const_iterator end = vector->end();  fieldIter < end;
			++fieldIter, ++id)
		{
			if (*fieldIter)
			{
				jrd_fld* field = *fieldIter;
				if (field->fld_name == name)
				{
					return id;
				}
			}
		}
	}

	// Not found.  Next, try system relations directly

	int id = -1;

	if (relation->rel_flags & REL_deleted)
		return id;

	AutoCacheRequest request(tdbb, irq_l_field, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$RELATION_FIELDS WITH
			X.RDB$RELATION_NAME EQ relation->rel_name.c_str() AND
			X.RDB$FIELD_ID NOT MISSING AND
			X.RDB$FIELD_NAME EQ name.c_str()
	{
		id = X.RDB$FIELD_ID;
	}
	END_FOR

	return id;
}


BlobFilter* MET_lookup_filter(thread_db* tdbb, SSHORT from, SSHORT to)
{
/**************************************
 *
 *      M E T _ l o o k u p _ f i l t e r
 *
 **************************************
 *
 * Functional description
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();

	FPTR_BFILTER_CALLBACK filter = NULL;
	BlobFilter* blf = NULL;

	AutoCacheRequest request(tdbb, irq_r_filters, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$FILTERS WITH X.RDB$INPUT_SUB_TYPE EQ from AND
		X.RDB$OUTPUT_SUB_TYPE EQ to
	{
		filter = (FPTR_BFILTER_CALLBACK)
			Module::lookup(X.RDB$MODULE_NAME, X.RDB$ENTRYPOINT, dbb);
		if (filter)
		{
			blf = FB_NEW_POOL(*dbb->dbb_permanent) BlobFilter(*dbb->dbb_permanent);
			blf->blf_next = NULL;
			blf->blf_from = from;
			blf->blf_to = to;
			blf->blf_filter = filter;
			blf->blf_exception_message.printf(EXCEPTION_MESSAGE,
					X.RDB$FUNCTION_NAME, X.RDB$ENTRYPOINT, X.RDB$MODULE_NAME);
		}
	}
	END_FOR

	return blf;
}


bool MET_load_generator(thread_db* tdbb, GeneratorItem& item, bool* sysGen, SLONG* step)
{
/**************************************
 *
 *      M E T _ l o a d _ g e n e r a t o r
 *
 **************************************
 *
 * Functional description
 *      Lookup generator ID by its name and load its metadata into the passed object.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	if (item.name == MASTER_GENERATOR)
	{
		item.id = 0;
		if (sysGen)
			*sysGen = true;
		if (step)
			*step = 1;
		return true;
	}

	AutoCacheRequest request(tdbb, irq_r_gen_id, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$GENERATORS WITH X.RDB$GENERATOR_NAME EQ item.name.c_str()
	{
		item.id = X.RDB$GENERATOR_ID;
		item.secName = X.RDB$SECURITY_CLASS;
		if (sysGen)
			*sysGen = (X.RDB$SYSTEM_FLAG == fb_sysflag_system);
		if (step)
			*step = X.RDB$GENERATOR_INCREMENT;

		return true;
	}
	END_FOR

	return false;
}

SLONG MET_lookup_generator(thread_db* tdbb, const MetaName& name, bool* sysGen, SLONG* step)
{
/**************************************
 *
 *      M E T _ l o o k u p _ g e n e r a t o r
 *
 **************************************
 *
 * Functional description
 *      Lookup generator ID by its name.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	if (name == MASTER_GENERATOR)
	{
		if (sysGen)
			*sysGen = true;
		if (step)
			*step = 1;
		return 0;
	}

	AutoCacheRequest request(tdbb, irq_l_gen_id, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$GENERATORS WITH X.RDB$GENERATOR_NAME EQ name.c_str()
	{
		if (sysGen)
			*sysGen = (X.RDB$SYSTEM_FLAG == fb_sysflag_system);
		if (step)
			*step = X.RDB$GENERATOR_INCREMENT;

		return X.RDB$GENERATOR_ID;
	}
	END_FOR

	return -1;
}

bool MET_lookup_generator_id(thread_db* tdbb, SLONG gen_id, MetaName& name, bool* sysGen)
{
/**************************************
 *
 *      M E T _ l o o k u p _ g e n e r a t o r _ i d
 *
 **************************************
 *
 * Functional description
 *      Lookup generator (aka gen_id) by ID. It will load
 *		the name in the third parameter.
 *
 **************************************/
	SET_TDBB (tdbb);
	Attachment* attachment = tdbb->getAttachment();

	fb_assert(gen_id != 0);

	name = "";

	AutoCacheRequest request(tdbb, irq_r_gen_id_num, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		X IN RDB$GENERATORS WITH X.RDB$GENERATOR_ID EQ gen_id
	{
		if (sysGen)
			*sysGen = (X.RDB$SYSTEM_FLAG == fb_sysflag_system);

		name = X.RDB$GENERATOR_NAME;
	}
	END_FOR

	return name.hasData();
}

void MET_update_generator_increment(thread_db* tdbb, SLONG gen_id, SLONG step)
{
/**************************************
 *
 *      M E T _ u p d a t e _ g e n e r a t o r _ i n c r e m e n t
 *
 **************************************
 *
 * Functional description
 *      Update the step in a generator searched by ID.
 *		This function is for legacy code "SET GENERATOR TO value" only!
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	AutoCacheRequest request(tdbb, irq_upd_gen_id_increm, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		X IN RDB$GENERATORS WITH X.RDB$GENERATOR_ID EQ gen_id

		// We never accept changing the step in sys gens.
		if (X.RDB$SYSTEM_FLAG == fb_sysflag_system)
			return;

		MODIFY X
			X.RDB$GENERATOR_INCREMENT = step;
		END_MODIFY
	END_FOR
}


void MET_lookup_index(thread_db* tdbb,
						MetaName& index_name,
						const MetaName& relation_name,
						USHORT number)
{
/**************************************
 *
 *      M E T _ l o o k u p _ i n d e x
 *
 **************************************
 *
 * Functional description
 *      Lookup index name from relation and index number.
 *      Calling routine must pass a buffer of at least 32 bytes.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	index_name = "";

	AutoCacheRequest request(tdbb, irq_l_index, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$INDICES WITH X.RDB$RELATION_NAME EQ relation_name.c_str()
			AND X.RDB$INDEX_ID EQ number
	{
		index_name = X.RDB$INDEX_NAME;
	}
	END_FOR
}


SLONG MET_lookup_index_name(thread_db* tdbb,
							const MetaName& index_name,
							SLONG* relation_id, IndexStatus* status)
{
/**************************************
 *
 *      M E T _ l o o k u p _ i n d e x _ n a m e
 *
 **************************************
 *
 * Functional description
 *      Lookup index id from index name.
 *
 **************************************/
	SLONG id = -1;

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	AutoCacheRequest request(tdbb, irq_l_index_name, IRQ_REQUESTS);

	*status = MET_object_unknown;

	FOR(REQUEST_HANDLE request)
		X IN RDB$INDICES WITH
			X.RDB$INDEX_NAME EQ index_name.c_str()
	{
		if (X.RDB$INDEX_INACTIVE == 0)
			*status = MET_object_active;
		else if (X.RDB$INDEX_INACTIVE == 3)
			*status = MET_object_deferred_active;
		else
			*status = MET_object_inactive;

		id = X.RDB$INDEX_ID - 1;
		const jrd_rel* relation = MET_lookup_relation(tdbb, X.RDB$RELATION_NAME);
		*relation_id = relation->rel_id;
	}
	END_FOR

	return id;
}


void MET_lookup_index_expression(thread_db* tdbb, jrd_rel* relation, index_desc* idx)
{
/**************************************
*
*	M E T _ l o o k u p _ i n d e x _ e x p r e s s i o n
*
**************************************
*
* Functional description
*	Lookup information about an index, in
*	the metadata cache if possible.
*
**************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// Check the index blocks for the relation to see if we have a cached block

	IndexBlock* index_block;
	for (index_block = relation->rel_index_blocks; index_block; index_block = index_block->idb_next)
	{
		if (index_block->idb_id == idx->idx_id)
			break;
	}

	if (index_block && index_block->idb_expression)
	{
		idx->idx_expression = index_block->idb_expression;
		idx->idx_expression_statement = index_block->idb_expression_statement;
		memcpy(&idx->idx_expression_desc, &index_block->idb_expression_desc, sizeof(struct dsc));
		return;
	}

	if (!(relation->rel_flags & REL_scanned) || (relation->rel_flags & REL_being_scanned))
	{
		MET_scan_relation(tdbb, relation);
	}

	CompilerScratch* csb = NULL;
	AutoCacheRequest request(tdbb, irq_l_exp_index, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		IDX IN RDB$INDICES WITH
		IDX.RDB$RELATION_NAME EQ relation->rel_name.c_str() AND
		IDX.RDB$INDEX_ID EQ idx->idx_id + 1
	{
		if (idx->idx_expression_statement)
		{
			idx->idx_expression_statement->release(tdbb);
			idx->idx_expression_statement = NULL;
		}

		// parse the blr, making sure to create the resulting expression
		// tree and request in its own pool so that it may be cached
		// with the index block in the "permanent" metadata cache

		{ // scope
			Jrd::ContextPoolHolder context(tdbb, attachment->createPool());
			idx->idx_expression = static_cast<ValueExprNode*>(MET_parse_blob(
				tdbb, relation, &IDX.RDB$EXPRESSION_BLR, &csb,
				&idx->idx_expression_statement, false, false));
		} // end scope
	}
	END_FOR

	if (csb)
		idx->idx_expression->getDesc(tdbb, csb, &idx->idx_expression_desc);

	delete csb;

	// if there is no existing index block for this index, create
	// one and link it in with the index blocks for this relation

	if (!index_block)
		index_block = IDX_create_index_block(tdbb, relation, idx->idx_id);

	// if we can't get the lock, no big deal: just give up on caching the index info

	if (!LCK_lock(tdbb, index_block->idb_lock, LCK_SR, LCK_NO_WAIT))
	{
		// clear lock error from status vector
		fb_utils::init_status(tdbb->tdbb_status_vector);
		return;
	}

	// whether the index block already existed or was just created,
	// fill in the cached information about the index

	index_block->idb_expression = idx->idx_expression;
	index_block->idb_expression_statement = idx->idx_expression_statement;
	memcpy(&index_block->idb_expression_desc, &idx->idx_expression_desc, sizeof(struct dsc));
}


bool MET_lookup_partner(thread_db* tdbb, jrd_rel* relation, index_desc* idx, const TEXT* index_name)
{
/**************************************
 *
 *      M E T _ l o o k u p _ p a r t n e r
 *
 **************************************
 *
 * Functional description
 *      Find partner index participating in a
 *      foreign key relationship.
 *
 **************************************/

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	if (relation->rel_flags & REL_check_partners)
		scan_partners(tdbb, relation);

	if (idx->idx_flags & idx_foreign)
	{
		if (index_name)
		{
			// Since primary key index names aren't being cached, do a long
			// hard lookup. This is only called during index create for foreign keys.

			bool found = false;
			AutoRequest request;

			FOR(REQUEST_HANDLE request)
				IDX IN RDB$INDICES CROSS
					IND IN RDB$INDICES WITH
					IDX.RDB$RELATION_NAME EQ relation->rel_name.c_str() AND
					(IDX.RDB$INDEX_ID EQ idx->idx_id + 1 OR
					 IDX.RDB$INDEX_NAME EQ index_name) AND
					IND.RDB$INDEX_NAME EQ IDX.RDB$FOREIGN_KEY AND
					IND.RDB$UNIQUE_FLAG = 1
			{
				//// ASF: Hack fix for CORE-4304, until nasty interactions between dfw and met are not resolved.
				const jrd_rel* partner_relation = relation->rel_name == IND.RDB$RELATION_NAME ?
					relation : MET_lookup_relation(tdbb, IND.RDB$RELATION_NAME);

				if (partner_relation && !IDX.RDB$INDEX_INACTIVE && !IND.RDB$INDEX_INACTIVE)
				{
					idx->idx_primary_relation = partner_relation->rel_id;
					idx->idx_primary_index = IND.RDB$INDEX_ID - 1;
					fb_assert(idx->idx_primary_index != idx_invalid);
					found = true;
				}
			}
			END_FOR

			return found;
		}

		frgn* references = &relation->rel_foreign_refs;
		if (references->frgn_reference_ids)
		{
			for (unsigned int index_number = 0;
				index_number < references->frgn_reference_ids->count();
				index_number++)
			{
				if (idx->idx_id == (*references->frgn_reference_ids)[index_number])
				{
					idx->idx_primary_relation = (*references->frgn_relations)[index_number];
					idx->idx_primary_index = (*references->frgn_indexes)[index_number];
					return true;
				}
			}
		}
		return false;
	}
	else if (idx->idx_flags & (idx_primary | idx_unique))
	{
		const prim* dependencies = &relation->rel_primary_dpnds;
		if (dependencies->prim_reference_ids)
		{
			for (unsigned int index_number = 0;
				 index_number < dependencies->prim_reference_ids->count();
				 index_number++)
			{
				if (idx->idx_id == (*dependencies->prim_reference_ids)[index_number])
				{
					idx->idx_foreign_primaries = relation->rel_primary_dpnds.prim_reference_ids;
					idx->idx_foreign_relations = relation->rel_primary_dpnds.prim_relations;
					idx->idx_foreign_indexes = relation->rel_primary_dpnds.prim_indexes;
					return true;
				}
			}
		}
		return false;
	}

	return false;
}


jrd_prc* MET_lookup_procedure(thread_db* tdbb, const QualifiedName& name, bool noscan)
{
/**************************************
 *
 *      M E T _ l o o k u p _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *      Lookup procedure by name.  Name passed in is
 *      ASCIZ name.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	jrd_prc* check_procedure = NULL;

	// See if we already know the procedure by name
	for (jrd_prc** iter = attachment->att_procedures.begin(); iter != attachment->att_procedures.end(); ++iter)
	{
		jrd_prc* procedure = *iter;

		if (procedure && !(procedure->flags & Routine::FLAG_OBSOLETE) &&
			((procedure->flags & Routine::FLAG_SCANNED) || noscan) &&
			!(procedure->flags & Routine::FLAG_BEING_SCANNED) &&
			!(procedure->flags & Routine::FLAG_BEING_ALTERED))
		{
			if (procedure->getName() == name)
			{
				if (procedure->flags & Routine::FLAG_CHECK_EXISTENCE)
				{
					check_procedure = procedure;
					LCK_lock(tdbb, check_procedure->existenceLock, LCK_SR, LCK_WAIT);
					break;
				}

				return procedure;
			}
		}
	}

	// We need to look up the procedure name in RDB$PROCEDURES

	jrd_prc* procedure = NULL;

	AutoCacheRequest request(tdbb, irq_l_procedure, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		P IN RDB$PROCEDURES
		WITH P.RDB$PROCEDURE_NAME EQ name.identifier.c_str() AND
			 P.RDB$PACKAGE_NAME EQUIV NULLIF(name.package.c_str(), '')
	{
		procedure = MET_procedure(tdbb, P.RDB$PROCEDURE_ID, noscan, 0);
	}
	END_FOR

	if (check_procedure)
	{
		check_procedure->flags &= ~Routine::FLAG_CHECK_EXISTENCE;
		if (check_procedure != procedure)
		{
			LCK_release(tdbb, check_procedure->existenceLock);
			check_procedure->flags |= Routine::FLAG_OBSOLETE;
		}
	}

	return procedure;
}


jrd_prc* MET_lookup_procedure_id(thread_db* tdbb, USHORT id,
								 bool return_deleted, bool noscan, USHORT flags)
{
/**************************************
 *
 *      M E T _ l o o k u p _ p r o c e d u r e _ i d
 *
 **************************************
 *
 * Functional description
 *      Lookup procedure by id.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	jrd_prc* check_procedure = NULL;

	jrd_prc* procedure;

	if (id < (USHORT) attachment->att_procedures.getCount() && (procedure = attachment->att_procedures[id]) &&
		procedure->getId() == id &&
		!(procedure->flags & Routine::FLAG_BEING_SCANNED) &&
		((procedure->flags & Routine::FLAG_SCANNED) || noscan) &&
		!(procedure->flags & Routine::FLAG_BEING_ALTERED) &&
		(!(procedure->flags & Routine::FLAG_OBSOLETE) || return_deleted))
	{
		if (procedure->flags & Routine::FLAG_CHECK_EXISTENCE)
		{
			check_procedure = procedure;
			LCK_lock(tdbb, check_procedure->existenceLock, LCK_SR, LCK_WAIT);
		}
		else {
			return procedure;
		}
	}

	// We need to look up the procedure name in RDB$PROCEDURES

	procedure = NULL;

	AutoCacheRequest request(tdbb, irq_l_proc_id, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		P IN RDB$PROCEDURES WITH P.RDB$PROCEDURE_ID EQ id
	{
		procedure = MET_procedure(tdbb, P.RDB$PROCEDURE_ID, noscan, flags);
	}
	END_FOR

	if (check_procedure)
	{
		check_procedure->flags &= ~Routine::FLAG_CHECK_EXISTENCE;
		if (check_procedure != procedure)
		{
			LCK_release(tdbb, check_procedure->existenceLock);
			check_procedure->flags |= Routine::FLAG_OBSOLETE;
		}
	}

	return procedure;
}


jrd_rel* MET_lookup_relation(thread_db* tdbb, const MetaName& name)
{
/**************************************
 *
 *      M E T _ l o o k u p _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *      Lookup relation by name.  Name passed in is
 *      ASCIZ name.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// See if we already know the relation by name

	vec<jrd_rel*>* relations = attachment->att_relations;
	jrd_rel* check_relation = NULL;

	vec<jrd_rel*>::iterator ptr = relations->begin();
	for (const vec<jrd_rel*>::const_iterator end = relations->end(); ptr < end; ++ptr)
	{
		jrd_rel* const relation = *ptr;

		if (relation)
		{
			if (relation->rel_flags & REL_deleting)
				CheckoutLockGuard guard(tdbb, relation->rel_drop_mutex, FB_FUNCTION);

			if (!(relation->rel_flags & REL_deleted))
			{
				// dimitr: for non-system relations we should also check
				//		   REL_scanned and REL_being_scanned flags. Look
				//		   at MET_lookup_procedure for example.
				if (!(relation->rel_flags & REL_system) &&
					(!(relation->rel_flags & REL_scanned) || (relation->rel_flags & REL_being_scanned)))
				{
					continue;
				}

				if (relation->rel_name == name)
				{
					if (relation->rel_flags & REL_check_existence)
					{
						check_relation = relation;
						LCK_lock(tdbb, check_relation->rel_existence_lock, LCK_SR, LCK_WAIT);
						break;
					}

					return relation;
				}
			}
		}
	}

	// We need to look up the relation name in RDB$RELATIONS

	jrd_rel* relation = NULL;

	AutoCacheRequest request(tdbb, irq_l_relation, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$RELATIONS WITH X.RDB$RELATION_NAME EQ name.c_str()
	{
		relation = MET_relation(tdbb, X.RDB$RELATION_ID);
		if (relation->rel_name.length() == 0) {
			relation->rel_name = name;
		}

		relation->rel_flags |= get_rel_flags_from_FLAGS(X.RDB$FLAGS);

		if (!X.RDB$RELATION_TYPE.NULL)
		{
			relation->rel_flags |= MET_get_rel_flags_from_TYPE(X.RDB$RELATION_TYPE);
		}
	}
	END_FOR

	if (check_relation)
	{
		check_relation->rel_flags &= ~REL_check_existence;
		if (check_relation != relation)
		{
			LCK_release(tdbb, check_relation->rel_existence_lock);
			if (!(check_relation->rel_flags & REL_check_partners))
			{
				check_relation->rel_flags |= REL_check_partners;
				LCK_release(tdbb, check_relation->rel_partners_lock);
				check_relation->rel_flags &= ~REL_check_partners;
			}
			LCK_release(tdbb, check_relation->rel_rescan_lock);
			check_relation->rel_flags |= REL_deleted;
		}
	}

	return relation;
}


jrd_rel* MET_lookup_relation_id(thread_db* tdbb, SLONG id, bool return_deleted)
{
/**************************************
 *
 *      M E T _ l o o k u p _ r e l a t i o n _ i d
 *
 **************************************
 *
 * Functional description
 *      Lookup relation by id. Make sure it really exists.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* const attachment = tdbb->getAttachment();

	// System relations are above suspicion

	if (id < (int) rel_MAX)
	{
		fb_assert(id < MAX_USHORT);
		return MET_relation(tdbb, (USHORT) id);
	}

	jrd_rel* check_relation = NULL;
	jrd_rel* relation;
	vec<jrd_rel*>* vector = attachment->att_relations;
	if (vector && (id < (SLONG) vector->count()) && (relation = (*vector)[id]))
	{
		if (relation->rel_flags & REL_deleting)
			CheckoutLockGuard guard(tdbb, relation->rel_drop_mutex, FB_FUNCTION);

		if (relation->rel_flags & REL_deleted)
			return return_deleted ? relation : NULL;

		if (relation->rel_flags & REL_check_existence)
		{
			check_relation = relation;
			LCK_lock(tdbb, check_relation->rel_existence_lock, LCK_SR, LCK_WAIT);
		}
		else
			return relation;
	}

	// We need to look up the relation id in RDB$RELATIONS

	relation = NULL;

	AutoCacheRequest request(tdbb, irq_l_rel_id, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$RELATIONS WITH X.RDB$RELATION_ID EQ id
	{
		relation = MET_relation(tdbb, X.RDB$RELATION_ID);
		if (relation->rel_name.length() == 0) {
			relation->rel_name = X.RDB$RELATION_NAME;
		}

		relation->rel_flags |= get_rel_flags_from_FLAGS(X.RDB$FLAGS);

		if (!X.RDB$RELATION_TYPE.NULL)
		{
			relation->rel_flags |= MET_get_rel_flags_from_TYPE(X.RDB$RELATION_TYPE);
		}
	}
	END_FOR

	if (check_relation)
	{
		check_relation->rel_flags &= ~REL_check_existence;
		if (check_relation != relation)
		{
			LCK_release(tdbb, check_relation->rel_existence_lock);
			if (!(check_relation->rel_flags & REL_check_partners))
			{
				check_relation->rel_flags |= REL_check_partners;
				LCK_release(tdbb, check_relation->rel_partners_lock);
				check_relation->rel_flags &= ~REL_check_partners;
			}
			LCK_release(tdbb, check_relation->rel_rescan_lock);
			check_relation->rel_flags |= REL_deleted;
		}
	}

	return relation;
}


DmlNode* MET_parse_blob(thread_db*	tdbb,
				   jrd_rel*		relation,
				   bid*			blob_id,
				   CompilerScratch**		csb_ptr,
				   JrdStatement**	statementPtr,
				   const bool	trigger,
				   bool validationExpr)
{
/**************************************
 *
 *      M E T _ p a r s e _ b l o b
 *
 **************************************
 *
 * Functional description
 *      Parse blr, returning a compiler scratch block with the results.
 *
 * if ignore_perm is true then, the request generated must be set to
 *   ignore all permissions checks. In this case, we call PAR_blr
 *   passing it the csb_ignore_perm flag to generate a request
 *   which must go through without checking any permissions.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	blb* blob = blb::open(tdbb, attachment->getSysTransaction(), blob_id);
	ULONG length = blob->blb_length + 10;
	HalfStaticArray<UCHAR, 512> tmp;
	UCHAR* temp = tmp.getBuffer(length);
	length = blob->BLB_get_data(tdbb, temp, length);

	DmlNode* node = NULL;

	if (validationExpr)
	{
		// The set of MET parse functions needs a rework.
		// For now, our caller chain is not interested in the returned node.
		PAR_validation_blr(tdbb, relation, temp, length, NULL, csb_ptr, 0);
	}
	else
		node = PAR_blr(tdbb, relation, temp, length, NULL, csb_ptr, statementPtr, trigger, 0);

	return node;
}


void MET_parse_sys_trigger(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
 *
 *      M E T _ p a r s e _ s y s _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *      Parse the blr for a system relation's triggers.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();

	relation->rel_flags &= ~REL_sys_triggers;

	// Release any triggers in case of a rescan

	if (relation->rel_pre_store)
		MET_release_triggers(tdbb, &relation->rel_pre_store);
	if (relation->rel_post_store)
		MET_release_triggers(tdbb, &relation->rel_post_store);
	if (relation->rel_pre_erase)
		MET_release_triggers(tdbb, &relation->rel_pre_erase);
	if (relation->rel_post_erase)
		MET_release_triggers(tdbb, &relation->rel_post_erase);
	if (relation->rel_pre_modify)
		MET_release_triggers(tdbb, &relation->rel_pre_modify);
	if (relation->rel_post_modify)
		MET_release_triggers(tdbb, &relation->rel_post_modify);

	// No need to load triggers for ReadOnly databases, since
	// INSERT/DELETE/UPDATE statements are not going to be allowed
	// hvlad: GTT with ON COMMIT DELETE ROWS clause is writable

	if (dbb->readOnly() && !(relation->rel_flags & REL_temp_tran))
		return;

	relation->rel_flags |= REL_sys_trigs_being_loaded;

	AutoCacheRequest request(tdbb, irq_s_triggers2, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$RELATION_NAME = relation->rel_name.c_str()
		AND TRG.RDB$SYSTEM_FLAG = 1
	{
		const FB_UINT64 type = TRG.RDB$TRIGGER_TYPE;
		const USHORT trig_flags = TRG.RDB$FLAGS;
		const TEXT* name = TRG.RDB$TRIGGER_NAME;

		TrigVector** ptr;

		switch (type)
		{
		case 1:
			ptr = &relation->rel_pre_store;
			break;
		case 2:
			ptr = &relation->rel_post_store;
			break;
		case 3:
			ptr = &relation->rel_pre_modify;
			break;
		case 4:
			ptr = &relation->rel_post_modify;
			break;
		case 5:
			ptr = &relation->rel_pre_erase;
			break;
		case 6:
			ptr = &relation->rel_post_erase;
			break;
		default:
			ptr = NULL;
			break;
		}

		if (ptr)
		{
			blb* blob = blb::open(tdbb, attachment->getSysTransaction(), &TRG.RDB$TRIGGER_BLR);
			ULONG length = blob->blb_length + 10;
			HalfStaticArray<UCHAR, 128> blr;
			length = blob->BLB_get_data(tdbb, blr.getBuffer(length), length);

			USHORT par_flags = (USHORT) ((trig_flags & TRG_ignore_perm) ? csb_ignore_perm : 0);
			if (type & 1)
				par_flags |= csb_pre_trigger;
			else
				par_flags |= csb_post_trigger;

			JrdStatement* statement = NULL;

			{
				Jrd::ContextPoolHolder context(tdbb, attachment->createPool());
				PAR_blr(tdbb, relation, blr.begin(), length, NULL, NULL, &statement, true, par_flags);
			}

			statement->triggerName = name;

			statement->flags |= JrdStatement::FLAG_SYS_TRIGGER;
			if (trig_flags & TRG_ignore_perm)
				statement->flags |= JrdStatement::FLAG_IGNORE_PERM;

			save_trigger_data(tdbb, ptr, relation, statement, NULL, NULL, NULL, type, true, 0, "",
				"", NULL);
		}
	}
	END_FOR

	relation->rel_flags &= ~REL_sys_trigs_being_loaded;
}


void MET_post_existence(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
 *
 *      M E T _ p o s t _ e x i s t e n c e
 *
 **************************************
 *
 * Functional description
 *      Post an interest in the existence of a relation.
 *
 **************************************/
	SET_TDBB(tdbb);

	relation->rel_use_count++;

	if (!MET_lookup_relation_id(tdbb, relation->rel_id, false))
	{
		relation->rel_use_count--;
		ERR_post(Arg::Gds(isc_relnotdef) << Arg::Str(relation->rel_name));
	}
}


void MET_prepare(thread_db* tdbb, jrd_tra* transaction, USHORT length, const UCHAR* msg)
{
/**************************************
 *
 *      M E T _ p r e p a r e
 *
 **************************************
 *
 * Functional description
 *      Post a transaction description to RDB$TRANSACTIONS.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	AutoCacheRequest request(tdbb, irq_s_trans, IRQ_REQUESTS);

	STORE(REQUEST_HANDLE request) X IN RDB$TRANSACTIONS
	{
		X.RDB$TRANSACTION_ID = transaction->tra_number;
		X.RDB$TRANSACTION_STATE = RDB$TRANSACTIONS.RDB$TRANSACTION_STATE.LIMBO;
		blb* blob = blb::create(tdbb, attachment->getSysTransaction(), &X.RDB$TRANSACTION_DESCRIPTION);
		blob->BLB_put_segment(tdbb, msg, length);
		blob->BLB_close(tdbb);
	}
	END_STORE
}


jrd_prc* MET_procedure(thread_db* tdbb, USHORT id, bool noscan, USHORT flags)
{
/**************************************
 *
 *      M E T _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *      Find or create a procedure block for a given procedure id.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();

	if (id >= attachment->att_procedures.getCount())
		attachment->att_procedures.resize(id + 10);

	jrd_prc* procedure = attachment->att_procedures[id];

	if (procedure && !(procedure->flags & Routine::FLAG_OBSOLETE))
	{
		// Make sure PRC_being_scanned and PRC_scanned are not set at the same time

		fb_assert(!(procedure->flags & Routine::FLAG_BEING_SCANNED) ||
			!(procedure->flags & Routine::FLAG_SCANNED));

		/* To avoid scanning recursive procedure's blr recursively let's
		   make use of Routine::FLAG_BEING_SCANNED bit. Because this bit is set
		   later in the code, it is not set when we are here first time.
		   If (in case of rec. procedure) we get here second time it is
		   already set and we return half baked procedure.
		   In case of superserver this code is under the rec. mutex
		   protection, thus the only guy (thread) who can get here and see
		   FLAG_BEING_SCANNED bit set is the guy which started procedure scan
		   and currently holds the mutex.
		   In case of classic, there is always only one guy and if it
		   sees FLAG_BEING_SCANNED bit set it is safe to assume it is here
		   second time.

		   If procedure has already been scanned - return. This condition
		   is for those threads that did not find procedure in cache and
		   came here to get it from disk. But only one was able to lock
		   the mutex and do the scanning, others were waiting. As soon as
		   the first thread releases the mutex another thread gets in and
		   it would be just unfair to make it do the work again.
		 */

		if ((procedure->flags & Routine::FLAG_BEING_SCANNED) ||
			(procedure->flags & Routine::FLAG_SCANNED))
		{
			return procedure;
		}
	}

	if (!procedure)
	{
		procedure = FB_NEW_POOL(*attachment->att_pool) jrd_prc(*attachment->att_pool);
	}

	try {

	procedure->flags |= (Routine::FLAG_BEING_SCANNED | flags);
	procedure->flags &= ~Routine::FLAG_OBSOLETE;

	procedure->setId(id);
	attachment->att_procedures[id] = procedure;

	if (!procedure->existenceLock)
	{
		Lock* lock = FB_NEW_RPT(*attachment->att_pool, 0)
			Lock(tdbb, sizeof(SLONG), LCK_prc_exist, procedure, blocking_ast_procedure);
		procedure->existenceLock = lock;
		lock->lck_key.lck_long = procedure->getId();
	}

	LCK_lock(tdbb, procedure->existenceLock, LCK_SR, LCK_WAIT);

	if (!noscan)
	{
		AutoCacheRequest request(tdbb, irq_r_procedure, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request)
			P IN RDB$PROCEDURES WITH P.RDB$PROCEDURE_ID EQ procedure->getId()
		{
			if (procedure->getName().toString().length() == 0)
			{
				procedure->setName(QualifiedName(P.RDB$PROCEDURE_NAME,
					(P.RDB$PACKAGE_NAME.NULL ? "" : P.RDB$PACKAGE_NAME)));
			}

			procedure->setId(P.RDB$PROCEDURE_ID);

			if (!P.RDB$SECURITY_CLASS.NULL)
				procedure->setSecurityName(P.RDB$SECURITY_CLASS);
			else if (!P.RDB$PACKAGE_NAME.NULL)
			{
				AutoCacheRequest requestHandle(tdbb, irq_l_procedure_pkg_class, IRQ_REQUESTS);

				FOR (REQUEST_HANDLE requestHandle)
					PKG IN RDB$PACKAGES
					WITH PKG.RDB$PACKAGE_NAME EQ P.RDB$PACKAGE_NAME
				{
					if (!PKG.RDB$SECURITY_CLASS.NULL)
						procedure->setSecurityName(PKG.RDB$SECURITY_CLASS);
				}
				END_FOR
			}

			procedure->setImplemented(true);
			procedure->getInputFields().resize(P.RDB$PROCEDURE_INPUTS);
			procedure->getOutputFields().resize(P.RDB$PROCEDURE_OUTPUTS);
			procedure->setDefaultCount(0);

			AutoCacheRequest request2(tdbb, irq_r_params, IRQ_REQUESTS);

			const MetaName packageName(P.RDB$PACKAGE_NAME.NULL ? NULL : P.RDB$PACKAGE_NAME);

			FOR (REQUEST_HANDLE request2)
				PA IN RDB$PROCEDURE_PARAMETERS
				CROSS F IN RDB$FIELDS
				WITH F.RDB$FIELD_NAME = PA.RDB$FIELD_SOURCE AND
					 PA.RDB$PROCEDURE_NAME = P.RDB$PROCEDURE_NAME AND
					 PA.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
			{
				const SSHORT pa_collation_id_null = PA.RDB$COLLATION_ID.NULL;
				const SSHORT pa_collation_id = PA.RDB$COLLATION_ID;
				const SSHORT pa_default_value_null = PA.RDB$DEFAULT_VALUE.NULL;
				bid pa_default_value = pa_default_value_null ? F.RDB$DEFAULT_VALUE : PA.RDB$DEFAULT_VALUE;

				Array<NestConst<Parameter> >& paramArray = PA.RDB$PARAMETER_TYPE ?
					procedure->getOutputFields() : procedure->getInputFields();

				// should be error if field already exists
				Parameter* parameter = FB_NEW_POOL(*attachment->att_pool) Parameter(*attachment->att_pool);
				parameter->prm_number = PA.RDB$PARAMETER_NUMBER;
				paramArray[parameter->prm_number] = parameter;
				parameter->prm_name = PA.RDB$PARAMETER_NAME;
				parameter->prm_nullable = PA.RDB$NULL_FLAG.NULL || PA.RDB$NULL_FLAG == 0;	// ODS_11_1
				parameter->prm_mechanism = PA.RDB$PARAMETER_MECHANISM.NULL ?	// ODS_11_1
					prm_mech_normal : (prm_mech_t) PA.RDB$PARAMETER_MECHANISM;

				if (!PA.RDB$FIELD_SOURCE.NULL)
					parameter->prm_field_source = PA.RDB$FIELD_SOURCE;

				DSC_make_descriptor(&parameter->prm_desc, F.RDB$FIELD_TYPE,
									F.RDB$FIELD_SCALE, F.RDB$FIELD_LENGTH,
									F.RDB$FIELD_SUB_TYPE, F.RDB$CHARACTER_SET_ID,
									(pa_collation_id_null ? F.RDB$COLLATION_ID : pa_collation_id));

				// UNICODE_FSS_HACK
				if (F.RDB$SYSTEM_FLAG == 1)
					DataTypeUtilBase::adjustSysFieldLength(&parameter->prm_desc);

				if (PA.RDB$PARAMETER_TYPE == 0 &&
					(!pa_default_value_null ||
					 (fb_utils::implicit_domain(F.RDB$FIELD_NAME) && !F.RDB$DEFAULT_VALUE.NULL)))
				{
					procedure->setDefaultCount(procedure->getDefaultCount() + 1);
					MemoryPool* pool = attachment->createPool();
					Jrd::ContextPoolHolder context(tdbb, pool);

					try
					{
						parameter->prm_default_value = static_cast<ValueExprNode*>(
							MET_parse_blob(tdbb, NULL, &pa_default_value, NULL, NULL, false, false));
					}
					catch (const Exception&)
					{
						// Here we lose pools created for previous defaults.
						// Probably we should use common pool for defaults and procedure itself.

						attachment->deletePool(pool);
						throw;
					}
				}
			}
			END_FOR

			const bool external = !P.RDB$ENGINE_NAME.NULL;	// ODS_12_0

			Array<NestConst<Parameter> >& paramArray = procedure->getOutputFields();

			if (paramArray.hasData() && paramArray[0])
			{
				Format* format = Format::newFormat(
					*attachment->att_pool, procedure->getOutputFields().getCount());
				procedure->prc_record_format = format;
				ULONG length = FLAG_BYTES(format->fmt_count);
				Format::fmt_desc_iterator desc = format->fmt_desc.begin();
				Array<NestConst<Parameter> >::iterator ptr, end;
				for (ptr = paramArray.begin(), end = paramArray.end(); ptr < end; ++ptr, ++desc)
				{
					const Parameter* parameter = *ptr;
					// check for parameter to be null, this can only happen if the
					// parameter numbers get out of sync. This was added to fix bug
					// 10534. -Shaunak Mistry 12-May-99
					if (parameter)
					{
						*desc = parameter->prm_desc;
						length = MET_align(&(*desc), length);
						desc->dsc_address = (UCHAR *) (IPTR) length;
						length += desc->dsc_length;
					}
				}

				format->fmt_length = length;
			}

			procedure->prc_type = P.RDB$PROCEDURE_TYPE.NULL ?
				prc_legacy : (prc_t) P.RDB$PROCEDURE_TYPE;

			if (external || !P.RDB$PROCEDURE_BLR.NULL)
			{
				MemoryPool* const csb_pool = attachment->createPool();
				Jrd::ContextPoolHolder context(tdbb, csb_pool);

				try
				{
					AutoPtr<CompilerScratch> csb(CompilerScratch::newCsb(*csb_pool, 5));

					if (external)
					{
						HalfStaticArray<char, 512> body;

						if (!P.RDB$PROCEDURE_SOURCE.NULL)
						{
							blb* blob = blb::open(tdbb, attachment->getSysTransaction(),
								&P.RDB$PROCEDURE_SOURCE);
							ULONG len = blob->BLB_get_data(tdbb,
								(UCHAR*) body.getBuffer(blob->blb_length + 1), blob->blb_length + 1);
							body.begin()[MIN(blob->blb_length, len)] = '\0';
						}
						else
							body.getBuffer(1)[0] = '\0';

						dbb->dbb_extManager.makeProcedure(tdbb, csb, procedure, P.RDB$ENGINE_NAME,
							(P.RDB$ENTRYPOINT.NULL ? "" : P.RDB$ENTRYPOINT), body.begin());
					}
					else
					{
						try
						{
							procedure->parseBlr(tdbb, csb, &P.RDB$PROCEDURE_BLR, 
								P.RDB$DEBUG_INFO.NULL ? NULL : &P.RDB$DEBUG_INFO);
						}
						catch (const Exception& ex)
						{
							StaticStatusVector temp_status;
							ex.stuffException(temp_status);
							const string name = procedure->getName().toString();
							(Arg::Gds(isc_bad_proc_BLR) << Arg::Str(name)
								<< Arg::StatusVector(temp_status.begin())).raise();
						}
					}
				}
				catch (const Exception&)
				{
					if (procedure->getStatement())
						procedure->releaseStatement(tdbb);
					else
						attachment->deletePool(csb_pool);

					throw;
				}

				fb_assert(procedure->getStatement()->procedure == procedure);
			}
			else
			{
				RefPtr<MsgMetadata> inputMetadata(REF_NO_INCR,
					Routine::createMetadata(procedure->getInputFields()));
				procedure->setInputFormat(
					Routine::createFormat(procedure->getPool(), inputMetadata, false));

				RefPtr<MsgMetadata> outputMetadata(REF_NO_INCR,
					Routine::createMetadata(procedure->getOutputFields()));
				procedure->setOutputFormat(
					Routine::createFormat(procedure->getPool(), outputMetadata, true));

				procedure->setImplemented(false);
			}

			procedure->flags |= Routine::FLAG_SCANNED;

			if (!dbb->readOnly() &&
				!P.RDB$PROCEDURE_BLR.NULL &&
				!P.RDB$VALID_BLR.NULL && P.RDB$VALID_BLR == FALSE)
			{
				// If the BLR was marked as invalid but the procedure was compiled,
				// mark the BLR as valid.

				MODIFY P USING
					P.RDB$VALID_BLR = TRUE;
					P.RDB$VALID_BLR.NULL = FALSE;
				END_MODIFY
			}
		}
		END_FOR
	} // if !noscan

	// Make sure that it is really being scanned
	fb_assert(procedure->flags & Routine::FLAG_BEING_SCANNED);

	procedure->flags &= ~Routine::FLAG_BEING_SCANNED;

	}	// try
	catch (const Exception&)
	{
		procedure->flags &= ~(Routine::FLAG_BEING_SCANNED | Routine::FLAG_SCANNED);

		if (procedure->getExternal())
		{
			delete procedure->getExternal();
			procedure->setExternal(NULL);
		}

		if (procedure->existenceLock)
		{
			LCK_release(tdbb, procedure->existenceLock);
			delete procedure->existenceLock;
			procedure->existenceLock = NULL;
		}

		throw;
	}

	return procedure;
}

bool jrd_prc::reload(thread_db* tdbb)
{
	fb_assert(this->flags & Routine::FLAG_RELOAD);

	Attachment* attachment = tdbb->getAttachment();
	AutoCacheRequest request(tdbb, irq_r_proc_blr, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		P IN RDB$PROCEDURES WITH P.RDB$PROCEDURE_ID EQ this->getId()
	{
		MemoryPool* const csb_pool = attachment->createPool();
		Jrd::ContextPoolHolder context(tdbb, csb_pool);

		try
		{
			AutoPtr<CompilerScratch> csb(CompilerScratch::newCsb(*csb_pool, 5));

			try
			{
				this->parseBlr(tdbb, csb, &P.RDB$PROCEDURE_BLR,
					P.RDB$DEBUG_INFO.NULL ? NULL : &P.RDB$DEBUG_INFO);

				// parseBlr() above could set FLAG_RELOAD again
				return !(this->flags & Routine::FLAG_RELOAD);
			}
			catch (const Exception& ex)
			{
				StaticStatusVector temp_status;
				ex.stuffException(temp_status);

				const string name = this->getName().toString();
				(Arg::Gds(isc_bad_proc_BLR) << Arg::Str(name)
					<< Arg::StatusVector(temp_status.begin())).raise();
			}
		}
		catch (const Exception&)
		{
			attachment->deletePool(csb_pool);
			throw;
		}
	}
	END_FOR

	return false;
}

jrd_rel* MET_relation(thread_db* tdbb, USHORT id)
{
/**************************************
 *
 *      M E T _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *      Find or create a relation block for a given relation id.
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* dbb = tdbb->getDatabase();
	CHECK_DBB(dbb);

	Attachment* attachment = tdbb->getAttachment();
	vec<jrd_rel*>* vector = attachment->att_relations;
	MemoryPool* pool = attachment->att_pool;

	if (!vector)
		vector = attachment->att_relations = vec<jrd_rel*>::newVector(*pool, id + 10);
	else if (id >= vector->count())
		vector->resize(id + 10);

	jrd_rel* relation = (*vector)[id];
	if (relation)
		return relation;

	// From ODS 9 onwards, the first 128 relation IDS have been
	// reserved for system relations
	const USHORT max_sys_rel = USER_DEF_REL_INIT_ID - 1;

	relation = FB_NEW_POOL(*pool) jrd_rel(*pool);
	(*vector)[id] = relation;
	relation->rel_id = id;

	{ // Scope block.
		Lock* lock = FB_NEW_RPT(*pool, 0)
			Lock(tdbb, sizeof(SLONG), LCK_rel_partners, relation, partners_ast_relation);
		relation->rel_partners_lock = lock;
		lock->lck_key.lck_long = relation->rel_id;
	}

	{ // Scope block.
		Lock* lock = FB_NEW_RPT(*pool, 0)
			Lock(tdbb, sizeof(SLONG), LCK_rel_rescan, relation, rescan_ast_relation);
		relation->rel_rescan_lock = lock;
		lock->lck_key.lck_long = relation->rel_id;
	}

	// This should check system flag instead.
	if (relation->rel_id <= max_sys_rel)
		return relation;

	{ // Scope block.
		Lock* lock = FB_NEW_RPT(*pool, 0)
			Lock(tdbb, sizeof(SLONG), LCK_rel_exist, relation, blocking_ast_relation);
		relation->rel_existence_lock = lock;
		lock->lck_key.lck_long = relation->rel_id;
	}

	relation->rel_flags |= (REL_check_existence | REL_check_partners);
	return relation;
}


void MET_release_existence(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
 *
 *      M E T _ r e l e a s e _ e x i s t e n c e
 *
 **************************************
 *
 * Functional description
 *      Release interest in relation. If no remaining interest
 *      and we're blocking the drop of the relation then release
 *      existence lock and mark deleted.
 *
 **************************************/
	if (relation->rel_use_count) {
		relation->rel_use_count--;
	}

	if (!relation->rel_use_count)
	{
		if (relation->rel_flags & REL_blocking) {
			LCK_re_post(tdbb, relation->rel_existence_lock);
		}

		if (relation->rel_file)
		{
			// close external file
			EXT_fini(relation, true);
		}
	}
}


void MET_revoke(thread_db* tdbb, jrd_tra* transaction, const MetaName& relation,
	const MetaName& revokee, const string& privilege)
{
/**************************************
 *
 *      M E T _ r e v o k e
 *
 **************************************
 *
 * Functional description
 *      Execute a recursive revoke.  This is called only when
 *      a revoked privilege had the grant option.
 *
 **************************************/
	SET_TDBB(tdbb);

	// See if the revokee still has the privilege.  If so, there's nothing to do

	USHORT count = 0;

	AutoCacheRequest request(tdbb, irq_revoke1, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIRST 1 P IN RDB$USER_PRIVILEGES WITH
			P.RDB$RELATION_NAME EQ relation.c_str() AND
			P.RDB$PRIVILEGE EQ privilege.c_str() AND
			P.RDB$USER EQ revokee.c_str()
	{
		++count;
	}
	END_FOR

	if (count)
		return;

	request.reset(tdbb, irq_revoke2, IRQ_REQUESTS);

	// User lost privilege.  Take it away from anybody he/she gave it to.

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		P IN RDB$USER_PRIVILEGES WITH
			P.RDB$RELATION_NAME EQ relation.c_str() AND
			P.RDB$PRIVILEGE EQ privilege.c_str() AND
			P.RDB$GRANTOR EQ revokee.c_str()
	{
		ERASE P;
	}
	END_FOR
}


void MET_scan_partners(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
 *
 *      M E T _ s c a n _ p a r t n e r s
 *
 **************************************
 *
 * Functional description
 *      Scan of foreign references on other relations' primary keys and
 *      scan of primary dependencies on relation's primary key.
 *
 **************************************/

	SET_TDBB(tdbb);

	if (relation->rel_flags & REL_check_partners)
		scan_partners(tdbb, relation);
}


void MET_scan_relation(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
 *
 *      M E T _ s c a n _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *      Scan a relation for view RecordSelExpr, computed by expressions, missing
 *      expressions, and validation expressions.
 *
 **************************************/
	SET_TDBB(tdbb);
	TrigVector* triggers[TRIGGER_MAX];
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();
	Jrd::ContextPoolHolder context(tdbb, attachment->att_pool);
	bool dependencies = false;
	bool sys_triggers = false;

	blb* blob = NULL;

	jrd_tra* depTrans = tdbb->getTransaction() ?
		tdbb->getTransaction() : attachment->getSysTransaction();

	// If anything errors, catch it to reset the scan flag.  This will
	// make sure that the error will be caught if the operation is tried again.

	try {

	if (relation->rel_flags & (REL_scanned | REL_deleted))
		return;

	relation->rel_flags |= REL_being_scanned;
	dependencies = (relation->rel_flags & REL_get_dependencies) ? true : false;
	sys_triggers = (relation->rel_flags & REL_sys_triggers) ? true : false;
	relation->rel_flags &= ~(REL_get_dependencies | REL_sys_triggers);

	for (USHORT itr = 0; itr < TRIGGER_MAX; ++itr)
		triggers[itr] = NULL;

	// Since this can be called recursively, find an inactive clone of the request

	AutoCacheRequest request(tdbb, irq_r_fields, IRQ_REQUESTS);
	CompilerScratch* csb = NULL;

	FOR(REQUEST_HANDLE request)
		REL IN RDB$RELATIONS WITH REL.RDB$RELATION_ID EQ relation->rel_id
	{
		// Pick up relation level stuff
		relation->rel_current_fmt = REL.RDB$FORMAT;
		vec<jrd_fld*>* vector = relation->rel_fields =
			vec<jrd_fld*>::newVector(*relation->rel_pool, relation->rel_fields, REL.RDB$FIELD_ID + 1);
		if (!REL.RDB$SECURITY_CLASS.NULL)
			relation->rel_security_name = REL.RDB$SECURITY_CLASS;

		relation->rel_name = REL.RDB$RELATION_NAME;
		relation->rel_owner_name = REL.RDB$OWNER_NAME;

		if (!REL.RDB$VIEW_BLR.isEmpty())
		{
			// parse the view blr, getting dependencies on relations, etc. at the same time

			DmlNode* rseNode;

			if (dependencies)
			{
				const MetaName depName(REL.RDB$RELATION_NAME);
				rseNode = MET_get_dependencies(tdbb, relation, NULL, 0, NULL, &REL.RDB$VIEW_BLR,
					NULL, &csb, depName, obj_view, 0, depTrans);
			}
			else
				rseNode = MET_parse_blob(tdbb, relation, &REL.RDB$VIEW_BLR, &csb, NULL, false, false);

			if (rseNode)
			{
				fb_assert(rseNode->getKind() == DmlNode::KIND_REC_SOURCE);
				relation->rel_view_rse = static_cast<RseNode*>(rseNode);
				fb_assert(relation->rel_view_rse->type == RseNode::TYPE);
			}
			else
				relation->rel_view_rse = NULL;

			// retrieve the view context names

			lookup_view_contexts(tdbb, relation);
		}

		relation->rel_flags |= REL_scanned;
		if (REL.RDB$EXTERNAL_FILE[0])
		{
			EXT_file(relation, REL.RDB$EXTERNAL_FILE); //, &REL.RDB$EXTERNAL_DESCRIPTION);
		}

		if (!REL.RDB$RELATION_TYPE.NULL)
		{
			switch (REL.RDB$RELATION_TYPE)
			{
				case rel_persistent:
					break;
				case rel_external:
					fb_assert(relation->rel_file);
					break;
				case rel_view:
					fb_assert(relation->rel_view_rse);
					fb_assert(relation->rel_flags & REL_jrd_view);
					relation->rel_flags |= REL_jrd_view;
					break;
				case rel_virtual:
					fb_assert(relation->rel_flags & REL_virtual);
					relation->rel_flags |= REL_virtual;
					break;
				case rel_global_temp_preserve:
					fb_assert(relation->rel_flags & REL_temp_conn);
					relation->rel_flags |= REL_temp_conn;
					break;
				case rel_global_temp_delete:
					fb_assert(relation->rel_flags & REL_temp_tran);
					relation->rel_flags |= REL_temp_tran;
					break;
				default:
					fb_assert(false);
			}
		}

		// Pick up field specific stuff

		blob = blb::open(tdbb, attachment->getSysTransaction(), &REL.RDB$RUNTIME);
		HalfStaticArray<UCHAR, 256> temp;
		UCHAR* const buffer = temp.getBuffer(blob->getMaxSegment() + 1U);

		jrd_fld* field = NULL;
		ArrayField* array = 0;
		USHORT view_context = 0;
		USHORT field_id = 0;
		for (;;)
		{
			USHORT length = blob->BLB_get_segment(tdbb, buffer, blob->getMaxSegment());
			if (blob->blb_flags & BLB_eof)
			{
				break;
			}
			USHORT n;
			buffer[length] = 0;
			UCHAR* p = (UCHAR*) &n;
			const UCHAR* q = buffer + 1;
			while (q < buffer + 1 + sizeof(SSHORT))
			{
				*p++ = *q++;
			}
			p = buffer + 1;
			--length;
			switch ((rsr_t) buffer[0])
			{
			case RSR_field_id:
				if (field && field->fld_security_name.length() == 0 && !REL.RDB$DEFAULT_CLASS.NULL)
				{
					field->fld_security_name = REL.RDB$DEFAULT_CLASS;
				}
				field_id = n;
				field = (*vector)[field_id];

				if (field)
				{
					field->fld_computation = NULL;
					field->fld_missing_value = NULL;
					field->fld_default_value = NULL;
					field->fld_validation = NULL;
					field->fld_not_null = NULL;
				}

				array = NULL;
				break;

			case RSR_field_name:
				if (field)
				{
					// The field exists.  If its name hasn't changed, then
					// there's no need to copy anything.

					if (field->fld_name == reinterpret_cast<char*>(p))
						break;

					field->fld_name = reinterpret_cast<char*>(p);
				}
				else
				{
					field = FB_NEW_POOL(*relation->rel_pool) jrd_fld(*relation->rel_pool);
					(*vector)[field_id] = field;
					field->fld_name = reinterpret_cast<char*>(p);
				}

				// CVC: Be paranoid and allow the possible trigger(s) to have a
				//   not null security class to work on, even if we only take it
				//   from the relation itself.
				if (field->fld_security_name.length() == 0 && !REL.RDB$DEFAULT_CLASS.NULL)
				{
					field->fld_security_name = REL.RDB$DEFAULT_CLASS;
				}

				break;

			case RSR_view_context:
				view_context = n;
				break;

			case RSR_base_field:
				if (dependencies)
				{
					csb->csb_g_flags |= csb_get_dependencies;
					field->fld_source = PAR_make_field(tdbb, csb, view_context, (TEXT*) p);
					const MetaName depName(REL.RDB$RELATION_NAME);
					store_dependencies(tdbb, csb, 0, depName, obj_view, depTrans);
				}
				else
					field->fld_source = PAR_make_field(tdbb, csb, view_context, (TEXT*) p);
				break;

			case RSR_computed_blr:
				{
					AutoSetRestoreFlag<USHORT> flag(&field->fld_flags, FLD_parse_computed, true);

					DmlNode* nod = dependencies ?
						MET_get_dependencies(tdbb, relation, p, length, csb, NULL, NULL, NULL,
							field->fld_name, obj_computed, csb_computed_field, depTrans) :
						PAR_blr(tdbb, relation, p, length, csb, NULL, NULL, false, csb_computed_field);

					field->fld_computation = static_cast<ValueExprNode*>(nod);
				}
				break;

			case RSR_missing_value:
				field->fld_missing_value = static_cast<ValueExprNode*>(
					PAR_blr(tdbb, relation, p, length, csb, NULL, NULL, false, 0));
				break;

			case RSR_default_value:
				field->fld_default_value = static_cast<ValueExprNode*>(
					PAR_blr(tdbb, relation, p, length, csb, NULL, NULL, false, 0));
				break;

			case RSR_validation_blr:
				// AB: 2005-04-25   bug SF#1168898
				// Ignore validation for VIEWs, because fields (domains) which are
				// defined with CHECK constraints and have sub-selects should at least
				// be parsed without view-context information. With view-context
				// information the context-numbers are wrong.
				// Because a VIEW can't have a validation section i ignored the whole call.
				if (!csb)
				{
					field->fld_validation = PAR_validation_blr(tdbb, relation, p, length, csb,
						NULL, csb_validation);
				}
				break;

			case RSR_field_not_null:
				field->fld_not_null = PAR_validation_blr(tdbb, relation, p, length, csb,
					NULL, csb_validation);
				break;

			case RSR_security_class:
				field->fld_security_name = (const TEXT*) p;
				break;

			case RSR_trigger_name:
				MET_load_trigger(tdbb, relation, (const TEXT*) p, triggers);
				break;

			case RSR_dimensions:
				field->fld_array = array = FB_NEW_RPT(*relation->rel_pool, n) ArrayField();
				array->arr_desc.iad_dimensions = n;
				break;

			case RSR_array_desc:
				if (array)
					memcpy(&array->arr_desc, p, length);
				break;

			case RSR_field_generator_name:
				field->fld_generator_name = (const TEXT*) p;
				break;

			default:    // Shut up compiler warning
				break;
			}
		}
		blob->BLB_close(tdbb);
		blob = 0;

		if (field && field->fld_security_name.length() == 0 && !REL.RDB$DEFAULT_CLASS.NULL)
		{
			field->fld_security_name = REL.RDB$DEFAULT_CLASS;
		}
	}
	END_FOR

	delete csb;

	// release any triggers in case of a rescan, but not if the rescan
	// hapenned while system triggers were being loaded.

	if (!(relation->rel_flags & REL_sys_trigs_being_loaded))
	{
		// if we are scanning a system relation during loading the system
		// triggers, (during parsing its blr actually), we must not release the
		// existing system triggers; because we have already set the
		// relation->rel_flag to not have REL_sys_trig, so these
		// system triggers will not get loaded again. This fixes bug 8149.

		// We have just loaded the triggers onto the local vector triggers.
		// Its now time to place them at their rightful place ie the relation block.

		TrigVector* tmp_vector;

		tmp_vector = relation->rel_pre_store;
		relation->rel_pre_store = triggers[TRIGGER_PRE_STORE];
		MET_release_triggers(tdbb, &tmp_vector);

		tmp_vector = relation->rel_post_store;
		relation->rel_post_store = triggers[TRIGGER_POST_STORE];
		MET_release_triggers(tdbb, &tmp_vector);

		tmp_vector = relation->rel_pre_erase;
		relation->rel_pre_erase = triggers[TRIGGER_PRE_ERASE];
		MET_release_triggers(tdbb, &tmp_vector);

		tmp_vector = relation->rel_post_erase;
		relation->rel_post_erase = triggers[TRIGGER_POST_ERASE];
		MET_release_triggers(tdbb, &tmp_vector);

		tmp_vector = relation->rel_pre_modify;
		relation->rel_pre_modify = triggers[TRIGGER_PRE_MODIFY];
		MET_release_triggers(tdbb, &tmp_vector);

		tmp_vector = relation->rel_post_modify;
		relation->rel_post_modify = triggers[TRIGGER_POST_MODIFY];
		MET_release_triggers(tdbb, &tmp_vector);
	}

	LCK_lock(tdbb, relation->rel_rescan_lock, LCK_SR, LCK_WAIT);
	relation->rel_flags &= ~REL_being_scanned;

	relation->rel_current_format = NULL;

	}	// try
	catch (const Exception&)
	{
		relation->rel_flags &= ~(REL_being_scanned | REL_scanned);
		if (dependencies) {
			relation->rel_flags |= REL_get_dependencies;
		}
		if (sys_triggers) {
			relation->rel_flags |= REL_sys_triggers;
		}
		if (blob)
			blob->BLB_close(tdbb);

		throw;
	}
}


void MET_trigger_msg(thread_db* tdbb, string& msg, const MetaName& name,
	USHORT number)
{
/**************************************
 *
 *      M E T _ t r i g g e r _ m s g
 *
 **************************************
 *
 * Functional description
 *      Look up trigger message using trigger and abort code.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	AutoCacheRequest request(tdbb, irq_s_msgs, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		MSG IN RDB$TRIGGER_MESSAGES WITH
			MSG.RDB$TRIGGER_NAME EQ name.c_str() AND
			MSG.RDB$MESSAGE_NUMBER EQ number
	{
		msg = MSG.RDB$MESSAGE;
	}
	END_FOR

	msg.rtrim();
}


void MET_update_shadow(thread_db* tdbb, Shadow* shadow, USHORT file_flags)
{
/**************************************
 *
 *      M E T _ u p d a t e _ s h a d o w
 *
 **************************************
 *
 * Functional description
 *      Update the stored file flags for the specified shadow.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	AutoRequest handle;

	FOR(REQUEST_HANDLE handle)
		FIL IN RDB$FILES WITH FIL.RDB$SHADOW_NUMBER EQ shadow->sdw_number
	{
		MODIFY FIL USING
			FIL.RDB$FILE_FLAGS = file_flags;
		END_MODIFY
	}
	END_FOR
}


void MET_update_transaction(thread_db* tdbb, jrd_tra* transaction, const bool do_commit)
{
/**************************************
 *
 *      M E T _ u p d a t e _ t r a n s a c t i o n
 *
 **************************************
 *
 * Functional description
 *      Update a record in RDB$TRANSACTIONS.  If do_commit is true, this is a
 *      commit; otherwise it is a ROLLBACK.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	AutoCacheRequest request(tdbb, irq_m_trans, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		X IN RDB$TRANSACTIONS
		WITH X.RDB$TRANSACTION_ID EQ transaction->tra_number
	{
		if (do_commit && (transaction->tra_flags & TRA_prepare2))
			ERASE X
		else
		{
			MODIFY X
				X.RDB$TRANSACTION_STATE = do_commit ?
					RDB$TRANSACTIONS.RDB$TRANSACTION_STATE.COMMITTED :
					RDB$TRANSACTIONS.RDB$TRANSACTION_STATE.ROLLED_BACK;
			END_MODIFY
		}
	}
	END_FOR
}


static int blocking_ast_dsql_cache(void* ast_object)
{
/**************************************
 *
 *	b l o c k i n g _ a s t _ d s q l _ c a c h e
 *
 **************************************
 *
 * Functional description
 *	Someone is trying to drop an item from the DSQL cache.
 *	Mark the symbol as obsolete and release the lock.
 *
 **************************************/
	DSqlCacheItem* const item = static_cast<DSqlCacheItem*>(ast_object);

	try
	{
		Database* const dbb = item->lock->lck_dbb;

		AsyncContextHolder tdbb(dbb, FB_FUNCTION, item->lock);

		item->obsolete = true;
		item->locked = false;
		LCK_release(tdbb, item->lock);
	}
	catch (const Exception&)
	{} // no-op

	return 0;
}


static DSqlCacheItem* get_dsql_cache_item(thread_db* tdbb, int type, const QualifiedName& name)
{
	Database* dbb = tdbb->getDatabase();
	Attachment* attachment = tdbb->getAttachment();

	string key((char*) &type, sizeof(type));
	int len = name.identifier.length();
	key.append((char*) &len, sizeof(len));
	key.append(name.identifier.c_str(), len);

	len = name.package.length();
	key.append((char*) &len, sizeof(len));
	key.append(name.package.c_str(), len);

	DSqlCacheItem* item = attachment->att_dsql_cache.put(key);
	if (item)
	{
		item->obsolete = false;
		item->locked = false;
		item->lock = FB_NEW_RPT(*attachment->att_pool, key.length())
			Lock(tdbb, key.length(), LCK_dsql_cache, item, blocking_ast_dsql_cache);
		memcpy(item->lock->getKeyString(), key.c_str(), key.length());
	}
	else
	{
		item = attachment->att_dsql_cache.get(key);
	}

	return item;
}


static int blocking_ast_procedure(void* ast_object)
{
/**************************************
 *
 *      b l o c k i n g _ a s t _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *      Someone is trying to drop a proceedure. If there
 *      are outstanding interests in the existence of
 *      the relation then just mark as blocking and return.
 *      Otherwise, mark the procedure block as questionable
 *      and release the procedure existence lock.
 *
 **************************************/
	jrd_prc* const procedure = static_cast<jrd_prc*>(ast_object);

	try
	{
		if (procedure->existenceLock)
		{
			Database* const dbb = procedure->existenceLock->lck_dbb;

			AsyncContextHolder tdbb(dbb, FB_FUNCTION, procedure->existenceLock);

			LCK_release(tdbb, procedure->existenceLock);
		}
		procedure->flags |= Routine::FLAG_OBSOLETE;
	}
	catch (const Exception&)
	{} // no-op

	return 0;
}


static int blocking_ast_relation(void* ast_object)
{
/**************************************
 *
 *      b l o c k i n g _ a s t _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *      Someone is trying to drop a relation. If there
 *      are outstanding interests in the existence of
 *      the relation then just mark as blocking and return.
 *      Otherwise, mark the relation block as questionable
 *      and release the relation existence lock.
 *
 **************************************/
	jrd_rel* const relation = static_cast<jrd_rel*>(ast_object);

	try
	{
		if (relation->rel_existence_lock)
		{
			Database* const dbb = relation->rel_existence_lock->lck_dbb;

			AsyncContextHolder tdbb(dbb, FB_FUNCTION, relation->rel_existence_lock);

			if (relation->rel_use_count)
				relation->rel_flags |= REL_blocking;
			else if (!(relation->rel_flags & REL_deleting))
			{
				relation->rel_flags &= ~REL_blocking;
				relation->rel_flags |= REL_check_existence;
				LCK_release(tdbb, relation->rel_existence_lock);
			}
		}
	}
	catch (const Exception&)
	{} // no-op

	return 0;
}


static int partners_ast_relation(void* ast_object)
{
	jrd_rel* const relation = static_cast<jrd_rel*>(ast_object);

	try
	{
		Database* const dbb = relation->rel_partners_lock->lck_dbb;

		AsyncContextHolder tdbb(dbb, FB_FUNCTION, relation->rel_partners_lock);

		if (!(relation->rel_flags & REL_check_partners))
		{
			relation->rel_flags |= REL_check_partners;
			LCK_release(tdbb, relation->rel_partners_lock);
		}
	}
	catch (const Exception&)
	{} // no-op

	return 0;
}


static int rescan_ast_relation(void* ast_object)
{
	jrd_rel* const relation = static_cast<jrd_rel*>(ast_object);

	try
	{
		Database* const dbb = relation->rel_rescan_lock->lck_dbb;

		AsyncContextHolder tdbb(dbb, FB_FUNCTION, relation->rel_rescan_lock);

		LCK_release(tdbb, relation->rel_rescan_lock);
		relation->rel_flags &= ~REL_scanned;
	}
	catch (const Firebird::Exception&)
	{} // no-op

	return 0;
}


static ULONG get_rel_flags_from_FLAGS(USHORT flags)
{
/**************************************
 *
 *      g e t _ r e l _ f l a g s _ f r o m _ F L A G S
 *
 **************************************
 *
 * Functional description
 *      Get rel_flags from RDB$FLAGS
 *
 **************************************/
	ULONG ret = 0;

	if (flags & REL_sql) {
		ret |= REL_sql_relation;
	}

	return ret;
}


ULONG MET_get_rel_flags_from_TYPE(USHORT type)
{
/**************************************
 *
 *      M E T _g e t _ r e l _ f l a g s _ f r o m _ T Y P E
 *
 **************************************
 *
 * Functional description
 *      Get rel_flags from RDB$RELATION_TYPE
 *
 **************************************/
	ULONG ret = 0;

	switch (type)
	{
		case rel_persistent:
			break;
		case rel_external:
			break;
		case rel_view:
			ret |= REL_jrd_view;
			break;
		case rel_virtual:
			ret |= REL_virtual;
			break;
		case rel_global_temp_preserve:
			ret |= REL_temp_conn;
			break;
		case rel_global_temp_delete:
			ret |= REL_temp_tran;
			break;
		default:
			fb_assert(false);
	}

	return ret;
}


static void get_trigger(thread_db* tdbb, jrd_rel* relation,
						bid* blob_id, bid* debug_blob_id, TrigVector** ptr,
						const TEXT* name, FB_UINT64 type,
						bool sys_trigger, USHORT flags,
						const MetaName& engine, const string& entryPoint,
						const bid* body)
{
/**************************************
 *
 *      g e t _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *      Get trigger.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	if (blob_id->isEmpty() && (engine.isEmpty() || entryPoint.isEmpty()))
		return;

	blb* blrBlob = NULL;
	blb* debugInfoBlob = NULL;

	if (!blob_id->isEmpty())
		blrBlob = blb::open(tdbb, attachment->getSysTransaction(), blob_id);

	if (!debug_blob_id->isEmpty())
		debugInfoBlob = blb::open(tdbb, attachment->getSysTransaction(), debug_blob_id);

	save_trigger_data(tdbb, ptr, relation, NULL, blrBlob, debugInfoBlob,
					  name, type, sys_trigger, flags, engine, entryPoint, body);
}


static bool get_type(thread_db* tdbb, USHORT* id, const UCHAR* name, const TEXT* field)
{
/**************************************
 *
 *      g e t _ t y p e
 *
 **************************************
 *
 * Functional description
 *      Resoved a symbolic name in RDB$TYPES.  Returned the value
 *      defined for the name in (*id).  Don't touch (*id) if you
 *      don't find the name.
 *
 *      Return (1) if found, (0) otherwise.
 *
 **************************************/
	UCHAR buffer[MAX_SQL_IDENTIFIER_SIZE];			// BASED ON RDB$TYPE_NAME

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	fb_assert(id != NULL);
	fb_assert(name != NULL);
	fb_assert(field != NULL);

	// Force key to uppercase, following C locale rules for uppercase
	UCHAR* p;
	for (p = buffer; *name && p < buffer + sizeof(buffer) - 1; p++, name++)
	{
		*p = UPPER7(*name);
	}
	*p = 0;

	// Try for exact name match

	bool found = false;

	AutoRequest handle;

	FOR(REQUEST_HANDLE handle)
		FIRST 1 T IN RDB$TYPES WITH
			T.RDB$FIELD_NAME EQ field AND
			T.RDB$TYPE_NAME EQ buffer
	{
		found = true;
		*id = T.RDB$TYPE;
	}
	END_FOR

	return found;
}


static void lookup_view_contexts( thread_db* tdbb, jrd_rel* view)
{
/**************************************
 *
 *      l o o k u p _ v i e w _ c o n t e x t s
 *
 **************************************
 *
 * Functional description
 *      Lookup view contexts and store in a sorted
 *      array on the relation block.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();
	AutoCacheRequest request(tdbb, irq_view_context, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		V IN RDB$VIEW_RELATIONS WITH
			V.RDB$VIEW_NAME EQ view->rel_name.c_str()
			SORTED BY V.RDB$VIEW_CONTEXT
	{
		// trim trailing spaces
		fb_utils::exact_name_limit(V.RDB$CONTEXT_NAME, sizeof(V.RDB$CONTEXT_NAME));

		ViewContext* view_context = FB_NEW_POOL(*view->rel_pool)
			ViewContext(*view->rel_pool,
				V.RDB$CONTEXT_NAME, V.RDB$RELATION_NAME, V.RDB$VIEW_CONTEXT,
				(V.RDB$CONTEXT_TYPE.NULL ? VCT_TABLE : ViewContextType(V.RDB$CONTEXT_TYPE)));

		view->rel_view_contexts.add(view_context);
	}
	END_FOR
}


static void make_relation_scope_name(const TEXT* rel_name, const USHORT rel_flags,
	string& str)
{
/**************************************
 *
 *	m a k e _ r e l a t i o n _ s c o p e _ n a m e
 *
 **************************************
 *
 * Functional description
 *	Make string with relation name and type
 *	of its temporary scope
 *
 **************************************/
	const char *scope = NULL;
	if (rel_flags & REL_temp_conn)
		scope = REL_SCOPE_GTT_PRESERVE;
	else if (rel_flags & REL_temp_tran)
		scope = REL_SCOPE_GTT_DELETE;
	else
		scope = REL_SCOPE_PERSISTENT;

	str.printf(scope, rel_name);
}


// Parses default BLR for a field.
static ValueExprNode* parse_field_default_blr(thread_db* tdbb, bid* blob_id)
{
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	CompilerScratch* csb = CompilerScratch::newCsb(*tdbb->getDefaultPool(), 5);

	blb* blob = blb::open(tdbb, attachment->getSysTransaction(), blob_id);
	ULONG length = blob->blb_length + 10;
	HalfStaticArray<UCHAR, 512> temp;

	length = blob->BLB_get_data(tdbb, temp.getBuffer(length), length);

	DmlNode* node = PAR_blr(tdbb, NULL, temp.begin(), length, NULL, &csb, NULL, false, 0);

	csb->csb_blr_reader = BlrReader();
	delete csb;

	return static_cast<ValueExprNode*>(node);
}


// Parses validation BLR for a field.
static BoolExprNode* parse_field_validation_blr(thread_db* tdbb, bid* blob_id, const MetaName name)
{
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	CompilerScratch* csb = CompilerScratch::newCsb(*tdbb->getDefaultPool(), 5, name);

	blb* blob = blb::open(tdbb, attachment->getSysTransaction(), blob_id);
	ULONG length = blob->blb_length + 10;
	HalfStaticArray<UCHAR, 512> temp;

	length = blob->BLB_get_data(tdbb, temp.getBuffer(length), length);

	BoolExprNode* expr = PAR_validation_blr(tdbb, NULL,
		temp.begin(), length, NULL, &csb, 0);

	csb->csb_blr_reader = BlrReader();
	delete csb;

	return expr;
}


void MET_release_trigger(thread_db* tdbb, TrigVector** vector_ptr, const MetaName& name)
{
/***********************************************
 *
 *      M E T _ r e l e a s e _ t r i g g e r
 *
 ***********************************************
 *
 * Functional description
 *      Release a specified trigger.
 *      If trigger are still active let someone
 *      else do the work.
 *
 **************************************/
	if (!*vector_ptr)
		return;

	TrigVector& vector = **vector_ptr;

	SET_TDBB(tdbb);

	for (FB_SIZE_T i = 0; i < vector.getCount(); ++i)
	{
		if (vector[i].name == name)
		{
			JrdStatement* stmt = vector[i].statement;
			if (stmt)
			{
				if (stmt->isActive())
					break;
				stmt->release(tdbb);
			}
			vector.remove(i);
			break;
		}
	}
}


void MET_release_triggers( thread_db* tdbb, TrigVector** vector_ptr)
{
/***********************************************
 *
 *      M E T _ r e l e a s e _ t r i g g e r s
 *
 ***********************************************
 *
 * Functional description
 *      Release a possibly null vector of triggers.
 *      If triggers are still active let someone
 *      else do the work.
 *
 **************************************/
	TrigVector* vector = *vector_ptr;

	if (!vector)
		return;

	SET_TDBB(tdbb);

	*vector_ptr = NULL;

	for (FB_SIZE_T i = 0; i < vector->getCount(); i++)
	{
		JrdStatement* stmt = (*vector)[i].statement;
		if (stmt && stmt->isActive())
			return;
	}

	vector->release(tdbb);
}


static bool resolve_charset_and_collation(thread_db* tdbb,
										  USHORT* id,
										  const UCHAR* charset,
										  const UCHAR* collation)
{
/**************************************
 *
 *      r e s o l v e _ c h a r s e t _ a n d _ c o l l a t i o n
 *
 **************************************
 *
 * Functional description
 *      Given ASCII7 name of charset & collation
 *      resolve the specification to a Character set id.
 *      This character set id is also the id of the text_object
 *      that implements the C locale for the Character set.
 *
 * Inputs:
 *      (charset)
 *              ASCII7z name of character set.
 *              NULL (implying unspecified) means use the character set
 *              for defined for (collation).
 *
 *      (collation)
 *              ASCII7z name of collation.
 *              NULL means use the default collation for (charset).
 *
 * Outputs:
 *      (*id)
 *              Set to character set specified by this name (low byte)
 *              Set to collation specified by this name (high byte).
 *
 * Return:
 *      true if no errors (and *id is set).
 *      false if either name not found.
 *        or if names found, but the collation isn't for the specified
 *        character set.
 *
 **************************************/
	bool found = false;

	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	fb_assert(id != NULL);

	AutoRequest handle;

	if (!collation)
	{
		if (charset == NULL)
			charset = (const UCHAR*) DEFAULT_CHARACTER_SET_NAME;

		if (attachment->att_charset_ids.get((const TEXT*) charset, *id))
			return true;

		USHORT charset_id = 0;
		if (get_type(tdbb, &charset_id, charset, "RDB$CHARACTER_SET_NAME"))
		{
			attachment->att_charset_ids.put((const TEXT*) charset, charset_id);
			*id = charset_id;
			return true;
		}

		// Charset name not found in the alias table - before giving up
		// try the character_set table

		FOR(REQUEST_HANDLE handle)
			FIRST 1 CS IN RDB$CHARACTER_SETS
				WITH CS.RDB$CHARACTER_SET_NAME EQ charset
		{
            found = true;
			attachment->att_charset_ids.put((const TEXT*) charset, CS.RDB$CHARACTER_SET_ID);
			*id = CS.RDB$CHARACTER_SET_ID;
		}
		END_FOR

		return found;
	}

	if (!charset)
	{
		FOR(REQUEST_HANDLE handle)
			FIRST 1 COL IN RDB$COLLATIONS
				WITH COL.RDB$COLLATION_NAME EQ collation
		{
			found = true;
			*id = COL.RDB$CHARACTER_SET_ID | (COL.RDB$COLLATION_ID << 8);
		}
		END_FOR

		return found;
	}

	FOR(REQUEST_HANDLE handle)
		FIRST 1 CS IN RDB$CHARACTER_SETS CROSS
			COL IN RDB$COLLATIONS OVER RDB$CHARACTER_SET_ID CROSS
			AL1 IN RDB$TYPES
			WITH AL1.RDB$FIELD_NAME EQ "RDB$CHARACTER_SET_NAME"
			AND AL1.RDB$TYPE_NAME EQ charset
			AND COL.RDB$COLLATION_NAME EQ collation
			AND AL1.RDB$TYPE EQ CS.RDB$CHARACTER_SET_ID
	{
        found = true;
		attachment->att_charset_ids.put((const TEXT*) charset, CS.RDB$CHARACTER_SET_ID);
		*id = CS.RDB$CHARACTER_SET_ID | (COL.RDB$COLLATION_ID << 8);
	}
	END_FOR

	return found;
}


static void save_trigger_data(thread_db* tdbb, TrigVector** ptr, jrd_rel* relation,
							  JrdStatement* statement, blb* blrBlob, blb* debugInfoBlob,
							  const TEXT* name, FB_UINT64 type,
							  bool sys_trigger, USHORT flags,
							  const MetaName& engine, const string& entryPoint,
							  const bid* body)
{
/**************************************
 *
 *      s a v e _ t r i g g e r _ d a t a
 *
 **************************************
 *
 * Functional description
 *      Save trigger data to passed vector
 *
 **************************************/
	Attachment* attachment = tdbb->getAttachment();
	TrigVector* vector = *ptr;

	if (!vector)
	{
		MemoryPool* pool = relation ? relation->rel_pool : attachment->att_pool;
		vector = FB_NEW_POOL(*pool) TrigVector(*pool);
		vector->addRef();
		*ptr = vector;
	}

	Trigger& t = vector->add();

	if (blrBlob)
	{
		const ULONG length = blrBlob->blb_length + 10;
		UCHAR* const data = t.blr.getBuffer(length);
		t.blr.resize(blrBlob->BLB_get_data(tdbb, data, length));
	}

	if (debugInfoBlob)
	{
		const ULONG length = debugInfoBlob->blb_length + 10;
		UCHAR* const data = t.debugInfo.getBuffer(length);
		t.debugInfo.resize(debugInfoBlob->BLB_get_data(tdbb, data, length));
	}

	if (name)
		t.name = name;

	if (body)
	{
		blb* bodyBlob = blb::open(tdbb, attachment->getSysTransaction(), body);

		HalfStaticArray<char, 512> temp;
		ULONG length = bodyBlob->BLB_get_data(tdbb, (UCHAR*) temp.getBuffer(bodyBlob->blb_length),
			bodyBlob->blb_length);

		t.extBody.assign(temp.begin(), length);
	}

	t.type = type;
	t.flags = flags;
	t.compile_in_progress = false;
	t.sys_trigger = sys_trigger;
	t.statement = statement;
	t.relation = relation;
	t.engine = engine;
	t.entryPoint = entryPoint;
}


const Trigger* findTrigger(TrigVector* triggers, const MetaName& trig_name)
{
	if (triggers)
	{
		for (TrigVector::iterator t = triggers->begin(); t != triggers->end(); ++t)
		{
			if (t->name.compare(trig_name) == 0)
				return &(*t);
		}
	}

	return NULL;
}


void scan_partners(thread_db* tdbb, jrd_rel* relation)
{
/**************************************
 *
 *      s c a n _ p a r t n e r s
 *
 **************************************
 *
 * Functional description
 *      Scan of foreign references on other relations' primary keys and
 *      scan of primary dependencies on relation's primary key.
 *
 **************************************/
	Attachment* attachment = tdbb->getAttachment();

	while (relation->rel_flags & REL_check_partners)
	{
		relation->rel_flags &= ~REL_check_partners;
		LCK_lock(tdbb, relation->rel_partners_lock, LCK_SR, LCK_WAIT);

		if (relation->rel_flags & REL_check_partners)
			continue;

		AutoCacheRequest request(tdbb, irq_foreign1, IRQ_REQUESTS);
		frgn* references = &relation->rel_foreign_refs;
		int index_number = 0;

		if (references->frgn_reference_ids)
		{
			delete references->frgn_reference_ids;
			references->frgn_reference_ids = NULL;
		}
		if (references->frgn_relations)
		{
			delete references->frgn_relations;
			references->frgn_relations = NULL;
		}
		if (references->frgn_indexes)
		{
			delete references->frgn_indexes;
			references->frgn_indexes = NULL;
		}

		FOR(REQUEST_HANDLE request)
			IDX IN RDB$INDICES CROSS
			RC IN RDB$RELATION_CONSTRAINTS
			OVER RDB$INDEX_NAME CROSS
			IND IN RDB$INDICES WITH
			RC.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY AND
			IDX.RDB$RELATION_NAME EQ relation->rel_name.c_str() AND
			IND.RDB$INDEX_NAME EQ IDX.RDB$FOREIGN_KEY AND
			IDX.RDB$INDEX_ID > 0 AND
			IND.RDB$INDEX_ID > 0 AND
			IND.RDB$UNIQUE_FLAG = 1
		{
			//// ASF: Hack fix for CORE-4304, until nasty interactions between dfw and met are not resolved.
			const jrd_rel* partner_relation = relation->rel_name == IND.RDB$RELATION_NAME ?
				relation : MET_lookup_relation(tdbb, IND.RDB$RELATION_NAME);

			if (partner_relation && !IDX.RDB$INDEX_INACTIVE && !IND.RDB$INDEX_INACTIVE)
			{
				// This seems a good candidate for vcl.
				references->frgn_reference_ids =
					vec<int>::newVector(*relation->rel_pool, references->frgn_reference_ids,
					index_number + 1);

				(*references->frgn_reference_ids)[index_number] = IDX.RDB$INDEX_ID - 1;

				references->frgn_relations =
					vec<int>::newVector(*relation->rel_pool, references->frgn_relations,
					index_number + 1);

				(*references->frgn_relations)[index_number] = partner_relation->rel_id;

				references->frgn_indexes =
					vec<int>::newVector(*relation->rel_pool, references->frgn_indexes,
					index_number + 1);

				(*references->frgn_indexes)[index_number] = IND.RDB$INDEX_ID - 1;

				index_number++;
			}
		}
		END_FOR

		// Prepare for rescan of primary dependencies on relation's primary key and stale vectors.

		request.reset(tdbb, irq_foreign2, IRQ_REQUESTS);
		prim* dependencies = &relation->rel_primary_dpnds;
		index_number = 0;

		if (dependencies->prim_reference_ids)
		{
			delete dependencies->prim_reference_ids;
			dependencies->prim_reference_ids = NULL;
		}
		if (dependencies->prim_relations)
		{
			delete dependencies->prim_relations;
			dependencies->prim_relations = NULL;
		}
		if (dependencies->prim_indexes)
		{
			delete dependencies->prim_indexes;
			dependencies->prim_indexes = NULL;
		}

		FOR(REQUEST_HANDLE request)
			IDX IN RDB$INDICES CROSS
			IND IN RDB$INDICES WITH
			IDX.RDB$UNIQUE_FLAG = 1 AND
			IDX.RDB$INDEX_ID > 0 AND
			IND.RDB$INDEX_ID > 0 AND
			IDX.RDB$RELATION_NAME EQ relation->rel_name.c_str() AND
			IND.RDB$FOREIGN_KEY EQ IDX.RDB$INDEX_NAME
		{
			//// ASF: Hack fix for CORE-4304, until nasty interactions between dfw and met are not resolved.
			const jrd_rel* partner_relation = relation->rel_name == IND.RDB$RELATION_NAME ?
				relation : MET_lookup_relation(tdbb, IND.RDB$RELATION_NAME);

			if (partner_relation && !IDX.RDB$INDEX_INACTIVE && !IND.RDB$INDEX_INACTIVE)
			{
				dependencies->prim_reference_ids =
					vec<int>::newVector(*relation->rel_pool, dependencies->prim_reference_ids,
					index_number + 1);

				(*dependencies->prim_reference_ids)[index_number] = IDX.RDB$INDEX_ID - 1;

				dependencies->prim_relations =
					vec<int>::newVector(*relation->rel_pool, dependencies->prim_relations,
					index_number + 1);

				(*dependencies->prim_relations)[index_number] = partner_relation->rel_id;

				dependencies->prim_indexes =
					vec<int>::newVector(*relation->rel_pool, dependencies->prim_indexes,
					index_number + 1);

				(*dependencies->prim_indexes)[index_number] = IND.RDB$INDEX_ID - 1;

				index_number++;
			}
		}
		END_FOR
	}
}


static void store_dependencies(thread_db* tdbb,
							   CompilerScratch* csb,
							   const jrd_rel* dep_rel,
							   const MetaName& object_name,
							   int dependency_type,
							   jrd_tra* transaction)
{
/**************************************
 *
 *      s t o r e _ d e p e n d e n c i e s
 *
 **************************************
 *
 * Functional description
 *      Store records in RDB$DEPENDENCIES
 *      corresponding to the objects found during
 *      compilation of blr for a trigger, view, etc.
 *
 **************************************/
	MetaName name;

	SET_TDBB(tdbb);

	const Trigger* t = 0;
	const bool checkTableScope =
		(dependency_type == obj_computed) ||
		(dependency_type == obj_trigger) && (dep_rel != 0) &&
		(
			(t = findTrigger(dep_rel->rel_pre_erase, object_name)) ||
			(t = findTrigger(dep_rel->rel_pre_modify, object_name)) ||
			(t = findTrigger(dep_rel->rel_pre_store, object_name)) ||
			(t = findTrigger(dep_rel->rel_post_erase, object_name)) ||
			(t = findTrigger(dep_rel->rel_post_modify, object_name)) ||
			(t = findTrigger(dep_rel->rel_post_store, object_name))
		) && t && (t->sys_trigger);

	while (csb->csb_dependencies.hasData())
	{
		CompilerScratch::Dependency dependency = csb->csb_dependencies.pop();

		if (!dependency.relation && !dependency.function && !dependency.procedure &&
			!dependency.name && !dependency.number)
		{
			continue;
		}

		int dpdo_type = dependency.objType;
		jrd_rel* relation = NULL;
		const jrd_prc* procedure = NULL;
		const MetaName* dpdo_name = NULL;
		MetaName packageName;
		SubtypeInfo info;

		switch (dpdo_type)
		{
		case obj_relation:
			relation = dependency.relation;
			dpdo_name = &relation->rel_name;

			fb_assert(dep_rel || !checkTableScope);

			if (checkTableScope &&
				( (dep_rel->rel_flags & (REL_temp_tran | REL_temp_conn)) !=
				  (relation->rel_flags & (REL_temp_tran | REL_temp_conn)) ))
			{
				if ( !( // master is ON COMMIT PRESERVE, detail is ON COMMIT DELETE
						(dep_rel->rel_flags & REL_temp_tran) && (relation->rel_flags & REL_temp_conn) ||
						// computed field of a view
						(dependency_type == obj_computed) && (dep_rel->rel_view_rse != NULL)
					   ))
				{
					string sMaster, sChild;

					make_relation_scope_name(relation->rel_name.c_str(),
						relation->rel_flags, sMaster);
					make_relation_scope_name(dep_rel->rel_name.c_str(),
						dep_rel->rel_flags, sChild);

					ERR_post(Arg::Gds(isc_no_meta_update) <<
							 Arg::Gds(isc_met_wrong_gtt_scope) << Arg::Str(sChild) <<
																  Arg::Str(sMaster));
				}
			}

			MET_scan_relation(tdbb, relation);
			if (relation->rel_view_rse) {
				dpdo_type = obj_view;
			}
			break;
		case obj_procedure:
			procedure = dependency.procedure;
			dpdo_name = &procedure->getName().identifier;
			packageName = procedure->getName().package;
			break;
		case obj_collation:
			{
				const USHORT number = dependency.number;
				MET_get_char_coll_subtype_info(tdbb, number, &info);
				dpdo_name = &info.collationName;
			}
			break;
		case obj_exception:
			{
				const SLONG number = dependency.number;
				MET_lookup_exception(tdbb, number, name, NULL);
				dpdo_name = &name;
			}
			break;
		case obj_field:
			dpdo_name = dependency.name;
			break;
		case obj_generator:
			{
				// CVC: Here I'm going to track those pesky things named generators and UDFs.
				// But don't track sys gens.
				bool sysGen = false;
				const SLONG number = dependency.number;
				if (number == 0 || !MET_lookup_generator_id(tdbb, number, name, &sysGen) || sysGen)
					continue;
				dpdo_name = &name;
			}
			break;
		case obj_udf:
			{
				const Function* const udf = dependency.function;
				dpdo_name = &udf->getName().identifier;
				packageName = udf->getName().package;
			}
			break;
		case obj_index:
			name = *dependency.name;
			dpdo_name = &name;
			break;
		}

		MetaName field_name;

		if (dependency.subNumber || dependency.subName)
		{
			if (dependency.subNumber)
			{
				const SSHORT fld_id = (SSHORT) dependency.subNumber;
				if (relation)
				{
					const jrd_fld* field = MET_get_field(relation, fld_id);
					if (field)
						field_name = field->fld_name;
				}
				else if (procedure)
				{
					const Parameter* param = procedure->getOutputFields()[fld_id];
					// CVC: Setting the field var here didn't make sense alone,
					// so I thought the missing code was to try to extract
					// the field name that's in this case an output var from a proc.
					if (param)
						field_name = param->prm_name;
				}
			}
			else
				field_name = *dependency.subName;
		}

		if (field_name.hasData())
		{
			AutoCacheRequest request(tdbb, irq_c_deps_f, IRQ_REQUESTS);
			bool found = false;
			fb_assert(dpdo_name);

			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$DEPENDENCIES WITH
				X.RDB$DEPENDENT_NAME = object_name.c_str() AND
					X.RDB$DEPENDED_ON_NAME = dpdo_name->c_str() AND
					X.RDB$DEPENDED_ON_TYPE = dpdo_type AND
					X.RDB$FIELD_NAME = field_name.c_str() AND
					X.RDB$DEPENDENT_TYPE = dependency_type
			{
				found = true;
			}
			END_FOR

			if (found)
				continue;
		}
		else
		{
			AutoCacheRequest request(tdbb, irq_c_deps, IRQ_REQUESTS);
			bool found = false;

			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) X IN RDB$DEPENDENCIES WITH
				X.RDB$DEPENDENT_NAME = object_name.c_str() AND
					X.RDB$DEPENDED_ON_NAME = dpdo_name->c_str() AND
					X.RDB$DEPENDED_ON_TYPE = dpdo_type AND
					X.RDB$FIELD_NAME MISSING AND
					X.RDB$DEPENDENT_TYPE = dependency_type AND
					X.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
			{
				found = true;
			}
			END_FOR

			if (found)
				continue;
		}

		AutoCacheRequest request(tdbb, irq_s_deps, IRQ_REQUESTS);

		fb_assert(dpdo_name);

		STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) DEP IN RDB$DEPENDENCIES
		{
			strcpy(DEP.RDB$DEPENDENT_NAME, object_name.c_str());
			DEP.RDB$DEPENDED_ON_TYPE = dpdo_type;
			strcpy(DEP.RDB$DEPENDED_ON_NAME, dpdo_name->c_str());

			if (field_name.hasData())
			{
				DEP.RDB$FIELD_NAME.NULL = FALSE;
				strcpy(DEP.RDB$FIELD_NAME, field_name.c_str());
			}
			else
				DEP.RDB$FIELD_NAME.NULL = TRUE;

			if (packageName.hasData())
			{
				DEP.RDB$PACKAGE_NAME.NULL = FALSE;
				strcpy(DEP.RDB$PACKAGE_NAME, packageName.c_str());
			}
			else
				DEP.RDB$PACKAGE_NAME.NULL = TRUE;

			DEP.RDB$DEPENDENT_TYPE = dependency_type;
		}
		END_STORE
	}
}


static bool verify_TRG_ignore_perm(thread_db* tdbb, const MetaName& trig_name)
{
/*****************************************************
 *
 *      v e r i f y _ T R G _ i g n o r e  _ p e r m
 *
 *****************************************************
 *
 * Functional description
 *      Return true if this trigger can go through without any permission
 *      checks. Currently, the only class of triggers that can go
 *      through without permission checks are
 *      (a) two system triggers (RDB$TRIGGERS_34 and RDB$TRIGGERS_35)
 *      (b) those defined for referential integrity actions such as,
 *      set null, set default, and cascade.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// See if this is a system trigger, with the flag set as TRG_ignore_perm

	if (INI_get_trig_flags(trig_name.c_str()) & TRG_ignore_perm)
	{
		return true;
	}

	// See if this is a RI trigger

	AutoCacheRequest request(tdbb, irq_c_trg_perm, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		CHK IN RDB$CHECK_CONSTRAINTS CROSS
			REF IN RDB$REF_CONSTRAINTS WITH
			CHK.RDB$TRIGGER_NAME EQ trig_name.c_str() AND
			REF.RDB$CONSTRAINT_NAME = CHK.RDB$CONSTRAINT_NAME
	{
		fb_utils::exact_name_limit(REF.RDB$UPDATE_RULE, sizeof(REF.RDB$UPDATE_RULE));
		fb_utils::exact_name_limit(REF.RDB$DELETE_RULE, sizeof(REF.RDB$DELETE_RULE));

		if (!strcmp(REF.RDB$UPDATE_RULE, RI_ACTION_CASCADE) ||
			!strcmp(REF.RDB$UPDATE_RULE, RI_ACTION_NULL) ||
			!strcmp(REF.RDB$UPDATE_RULE, RI_ACTION_DEFAULT) ||
			!strcmp(REF.RDB$DELETE_RULE, RI_ACTION_CASCADE) ||
			!strcmp(REF.RDB$DELETE_RULE, RI_ACTION_NULL) ||
			!strcmp(REF.RDB$DELETE_RULE, RI_ACTION_DEFAULT))
		{
			return true;
		}

		return false;
	}
	END_FOR

	return false;
}

int MET_get_linger(thread_db* tdbb)
{
/**************************************
 *
 *	M E T _ g e t _ l i n g e r
 *
 **************************************
 *
 * Functional description
 *      Return linger value for current database
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();
	int rc = 0;

	AutoCacheRequest request(tdbb, irq_linger, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request)
		DAT IN RDB$DATABASE
	{
		if (!DAT.RDB$LINGER.NULL)
			rc = DAT.RDB$LINGER;
	}
	END_FOR

	return rc;
}
