/*
 *	PROGRAM:	JRD Access Method
 *	MODULE:		dfw.epp
 *	DESCRIPTION:	Deferred Work 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: Implement deferred check for udf usage
 * inside a procedure before dropping the udf and creating stub for future
 * processing of dependencies from dropped generators.
 *
 * 2001.8.12 Claudio Valderrama: find_depend_in_dfw() and other functions
 *   should respect identifiers with embedded blanks instead of chopping them
 *.
 * 2001.10.01 Claudio Valderrama: check constraints should fire AFTER the
 *   BEFORE <action> triggers; otherwise they allow invalid data to be stored.
 *   This is a quick fix for SF Bug #444463 until a more robust one is devised
 *   using trigger's rdb$flags or another mechanism.
 *
 * 2001.10.10 Ann Harrison:  Don't increment the format version unless the
 *   table is actually reformatted.  At the same time, break out some of
 *   the parts of make_version making some new subroutines with the goal
 *   of making make_version readable.
 *
 * 2001.10.18 Ann Harrison: some cleanup of trigger & constraint handling.
 *   it now appears to work correctly on new Firebird databases with lots
 *   of system types and on InterBase databases, without checking for
 *   missing source.
 *
 * 23-Feb-2002 Dmitry Yemanov - Events wildcarding
 *
 * 2002-02-24 Sean Leyne - Code Cleanup of old Win 3.1 port (WINDOWS_ONLY)
 *
 * Adriano dos Santos Fernandes
 *
 * 2008-03-16 Alex Peshkoff - avoid most of data modifications in system transaction.
 *	Problems took place when same data was modified in user transaction, and later -
 *	in system transaction. System transaction always performs updates in place,
 *	but when between RPB setup and actual modification garbage was collected (this
 *	was noticed with GC thread active, but may happen due to any read of the record),
 *	BUGCHECK(291) took place. To avoid that issue, it was decided not to modify data
 *	in system transaction. An exception is RDB$FORMATS relation, which is always modified
 *	by transaction zero. Also an aspect of 'dirty' access from system transaction was
 *	taken into an account in add_file(), make_version() and create_index().
 *
 */

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

#include "../common/classes/fb_string.h"
#include "../common/classes/VaryStr.h"
#include "../jrd/SystemPrivileges.h"
#include "../jrd/jrd.h"
#include "../jrd/val.h"
#include "../jrd/irq.h"
#include "../jrd/tra.h"
#include "../jrd/os/pio.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/lck.h"
#include "../jrd/sdw.h"
#include "../jrd/flags.h"
#include "../jrd/intl.h"
#include "../intl/charsets.h"
#include "../jrd/align.h"
#include "../common/gdsassert.h"
#include "../jrd/blb_proto.h"
#include "../jrd/btr_proto.h"
#include "../jrd/cch_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_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 "../yvalve/gds_proto.h"
#include "../jrd/grant_proto.h"
#include "../jrd/idx_proto.h"
#include "../jrd/intl_proto.h"
#include "../common/isc_f_proto.h"

#include "../jrd/lck_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/mov_proto.h"
#include "../jrd/pag_proto.h"
#include "../jrd/os/pio_proto.h"
#include "../jrd/rlck_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/sdw_proto.h"
#include "../jrd/tra_proto.h"
#include "../jrd/event_proto.h"
#include "../jrd/nbak.h"
#include "../jrd/trig.h"
#include "../jrd/GarbageCollector.h"
#include "../jrd/IntlManager.h"
#include "../jrd/UserManagement.h"
#include "../jrd/Function.h"
#include "../jrd/PreparedStatement.h"
#include "../jrd/ResultSet.h"
#include "../common/utils_proto.h"
#include "../common/classes/Hash.h"
#include "../jrd/CryptoManager.h"
#include "../jrd/Mapping.h"
#include "../jrd/shut_proto.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "gen/iberror.h"

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

// Define range of user relation ids

const int MIN_RELATION_ID	= rel_MAX;
const int MAX_RELATION_ID	= 32767;

const int COMPUTED_FLAG	= 128;
const int WAIT_PERIOD	= -1;

DATABASE DB = FILENAME "ODS.RDB";

using namespace Jrd;
using namespace Firebird;

namespace Jrd {

typedef HashTable<
	DeferredWork,
	DEFAULT_HASH_SIZE,
	DeferredWork,
	DefaultKeyValue<DeferredWork>,
	DeferredWork
> DfwHash;

// NS: This needs careful refactoring.
//
// Deferred work item:
// * Encapsulates deferred invocation of the task routine with a given set of
//   arguments.
// * Has code to maintain a doubly linked list of itself.
//
// These two functions need to be split, and linked list of custom entries can
// become generic.
//

class DeferredWork : public pool_alloc<type_dfw>,
					 public DfwHash::Entry
{
private:
	DeferredWork(const DeferredWork&);

public:
	enum dfw_t 		dfw_type;		// type of work deferred

private:
	DeferredWork***	dfw_end;
	DeferredWork**	dfw_prev;
	DeferredWork*	dfw_next;

public:
	Lock*			dfw_lock;		// relation creation lock
	Array<DeferredWork*>  dfw_args;	// arguments
	SavNumber		dfw_sav_number;	// save point number
	USHORT			dfw_id;			// object id, if appropriate
	USHORT			dfw_count;		// count of block posts
	string			dfw_name;		// name of object
	MetaName		dfw_package;	// package name
	SortedArray<int> dfw_ids;		// list of identifiers (or any numbers) needed by an action

public:
	DeferredWork(MemoryPool& p, DeferredWork*** end,
				 enum dfw_t t, USHORT id, SavNumber sn, const string& name,
				 const MetaName& package)
	  : dfw_type(t), dfw_end(end), dfw_prev(dfw_end ? *dfw_end : NULL),
		dfw_next(dfw_prev ? *dfw_prev : NULL), dfw_lock(NULL), dfw_args(p),
		dfw_sav_number(sn), dfw_id(id), dfw_count(1), dfw_name(p, name),
		dfw_package(p, package), dfw_ids(p)
	{
		// make previous element point to us
		if (dfw_prev)
		{
			*dfw_prev = this;
			// make next element (if present) to point to us
			if (dfw_next)
			{
				dfw_next->dfw_prev = &dfw_next;
			}
		}
	}

	~DeferredWork()
	{
		// if we are linked
		if (dfw_prev)
		{
			if (dfw_next)
			{
				// adjust previous pointer in next element ...
				dfw_next->dfw_prev = dfw_prev;
			}
			// adjust next pointer in previous element
			*dfw_prev = dfw_next;

			// Adjust end marker of the list
			if (*dfw_end == &dfw_next)
			{
				*dfw_end = dfw_prev;
			}
		}

		for (DeferredWork** itr = dfw_args.begin(); itr < dfw_args.end(); ++itr)
		{
			delete *itr;
		}

		if (dfw_lock)
		{
			LCK_release(JRD_get_thread_data(), dfw_lock);
			delete dfw_lock;
		}
	}

	DeferredWork* findArg(dfw_t type) const
	{
		for (DeferredWork* const* itr = dfw_args.begin(); itr < dfw_args.end(); ++itr)
		{
			DeferredWork* const arg = *itr;

			if (arg->dfw_type == type)
			{
				return arg;
			}
		}

		return NULL;
	}

	DeferredWork** getNextPtr()
	{
		return &dfw_next;
	}

	DeferredWork* getNext() const
	{
		return dfw_next;
	}

	// hash interface
	bool isEqual(const DeferredWork& work) const
	{
		if (dfw_type == work.dfw_type &&
			dfw_id == work.dfw_id &&
			dfw_name == work.dfw_name &&
			dfw_package == work.dfw_package &&
			dfw_sav_number == work.dfw_sav_number)
		{
			return true;
		}
		return false;
	}

	DeferredWork* get() { return this; }

	static FB_SIZE_T hash(const DeferredWork& work, FB_SIZE_T hashSize)
	{
		const int nameLimit = 32;
		char key[sizeof work.dfw_type + sizeof work.dfw_id + nameLimit];
		memset(key, 0, sizeof key);
		char* place = key;

		memcpy(place, &work.dfw_type, sizeof work.dfw_type);
		place += sizeof work.dfw_type;

		memcpy(place, &work.dfw_id, sizeof work.dfw_id);
		place += sizeof work.dfw_id;

		work.dfw_name.copyTo(place, nameLimit);	// It's good enough to have first 32 bytes

		return DefaultHash<DeferredWork>::hash(key, sizeof key, hashSize);
	}
};

class DfwSavePoint;

typedef HashTable<
	DfwSavePoint,
	DEFAULT_HASH_SIZE,
	SavNumber,
	DfwSavePoint
> DfwSavePointHash;

class DfwSavePoint : public DfwSavePointHash::Entry
{
	SavNumber dfw_sav_number;

public:
	DfwHash hash; // Deferred work items posted under this savepoint

	explicit DfwSavePoint(SavNumber number) : dfw_sav_number(number) { }

	// hash interface
	bool isEqual(const SavNumber& number) const
	{
		return dfw_sav_number == number;
	}

	DfwSavePoint* get() { return this; }

	static SavNumber generate(const DfwSavePoint& item)
	{
		return item.dfw_sav_number;
	}
};

// List of deferred work items (with per-savepoint break-down)
class DeferredJob
{
public:
	DfwSavePointHash hash; // Hash set of savepoints, that posted work
	DeferredWork* work;
	DeferredWork** end;

	DeferredJob() : work(NULL), end(&work) { }
};


// Lock relation with protected_read level or raise existing relation lock
// to this level to ensure nobody can write to this relation.
// Used when new index is built.
// releaseLock set to true if there was no existing lock before
class ProtectRelations
{
public:
	ProtectRelations(thread_db* tdbb, jrd_tra* transaction) :
		m_tdbb(tdbb),
		m_transaction(transaction),
		m_locks()
	{
	}

	ProtectRelations(thread_db* tdbb, jrd_tra* transaction, jrd_rel* relation) :
		m_tdbb(tdbb),
		m_transaction(transaction),
		m_locks()
	{
		addRelation(relation);
		lock();
	}

	~ProtectRelations()
	{
		unlock();
	}

	void addRelation(jrd_rel* relation)
	{
		FB_SIZE_T pos;
		if (!m_locks.find(relation->rel_id, pos))
			m_locks.insert(pos, relLock(relation));
	}

	bool exists(USHORT rel_id) const
	{
		FB_SIZE_T pos;
		return m_locks.find(rel_id, pos);
	}

	void lock()
	{
		relLock* item = m_locks.begin();
		const relLock* const end = m_locks.end();
		for (; item < end; item++)
			item->takeLock(m_tdbb, m_transaction);
	}

	void unlock()
	{
		relLock* item = m_locks.begin();
		const relLock* const end = m_locks.end();
		for (; item < end; item++)
			item->releaseLock(m_tdbb, m_transaction);
	}

private:
	struct relLock
	{
		relLock(jrd_rel* relation = NULL) :
			m_relation(relation),
			m_lock(NULL),
			m_release(false)
		{
		}

		void takeLock(thread_db* tdbb, jrd_tra* transaction);
		void releaseLock(thread_db* tdbb, jrd_tra* transaction);

		static const USHORT generate(const relLock& item)
		{
			return item.m_relation->rel_id;
		}

		jrd_rel* m_relation;
		Lock* m_lock;
		bool m_release;
	};

	thread_db* m_tdbb;
	jrd_tra* m_transaction;
	SortedArray<relLock, InlineStorage<relLock, 2>, USHORT, relLock> m_locks;
};

} // namespace Jrd

/*==================================================================
 *
 * NOTE:
 *
 *	The following functions required the same number of
 *	parameters to be passed.
 *
 *==================================================================
 */
static bool add_file(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool add_shadow(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_shadow(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool compute_security(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_index(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_index(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_index(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_relation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_relation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool scan_relation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_trigger(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_trigger(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_trigger(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_collation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_collation(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_exception(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool set_generator(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_generator(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool create_field(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_field(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool modify_field(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_global(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_parameter(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_rfr(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool make_version(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool add_difference(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool delete_difference(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool begin_backup(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool end_backup(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool check_not_null(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool store_view_context_type(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool user_management(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool drop_package_header(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool drop_package_body(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool grant_privileges(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool db_crypt(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool set_linger(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool clear_cache(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static bool change_repl_state(thread_db*, SSHORT, DeferredWork*, jrd_tra*);
static string remove_icu_info_from_attributes(const string&, const string&);

// ----------------------------------------------------------------

static bool create_expression_index(thread_db* tdbb, SSHORT phase, DeferredWork* work,
	jrd_tra* transaction);
static void check_computed_dependencies(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& fieldName);
static void check_dependencies(thread_db*, const TEXT*, const TEXT*, const TEXT*, int, jrd_tra*);
static void check_filename(thread_db*, const Firebird::string&, bool);
static void cleanup_index_creation(thread_db*, DeferredWork*, jrd_tra*);
static bool formatsAreEqual(const Format*, const Format*);
static bool	find_depend_in_dfw(thread_db*, TEXT*, USHORT, USHORT, jrd_tra*);
static void get_array_desc(thread_db*, const TEXT*, Ods::InternalArrayDesc*);
static void get_trigger_dependencies(DeferredWork*, bool, jrd_tra*);
static Format*	make_format(thread_db*, jrd_rel*, USHORT *, TemporaryField*);
static void put_summary_blob(thread_db* tdbb, blb*, enum rsr_t, bid*, jrd_tra*);
static void put_summary_record(thread_db* tdbb, blb*, enum rsr_t, const UCHAR*, ULONG);
static void	setup_array(thread_db*, blb*, const TEXT*, USHORT, TemporaryField*);
static blb*	setup_triggers(thread_db*, jrd_rel*, bool, TrigVector**, blb*);
static void	setup_trigger_details(thread_db*, jrd_rel*, blb*, TrigVector**, const TEXT*, bool);
static bool validate_text_type (thread_db*, const TemporaryField*);

static void check_partners(thread_db*, const USHORT);
static string get_string(const dsc* desc);
static void setupSpecificCollationAttributes(thread_db*, jrd_tra*, const USHORT, const char*, bool);

static ISC_STATUS getErrorCodeByObjectType(int obj_type)
{
	ISC_STATUS err_code = 0;

	switch (obj_type)
	{
	case obj_relation:
		err_code = isc_table_name;
		break;
	case obj_view:
		err_code = isc_view_name;
		break;
	case obj_procedure:
		err_code = isc_proc_name;
		break;
	case obj_collation:
		err_code = isc_collation_name;
		break;
	case obj_exception:
		err_code = isc_exception_name;
		break;
	case obj_field:
		err_code = isc_domain_name;
		break;
	case obj_generator:
		err_code = isc_generator_name;
		break;
	case obj_udf:
		err_code = isc_udf_name;
		break;
	case obj_index:
		err_code = isc_index_name;
		break;
	case obj_package_header:
	case obj_package_body:
		err_code = isc_package_name;
		break;
	default:
		fb_assert(false);
	}

	return err_code;
}

static void raiseDatabaseInUseError(bool timeout)
{
	if (timeout)
	{
		ERR_post(Arg::Gds(isc_no_meta_update) <<
				 Arg::Gds(isc_lock_timeout) <<
				 Arg::Gds(isc_obj_in_use) << Arg::Str("DATABASE"));
	}

	ERR_post(Arg::Gds(isc_no_meta_update) <<
			 Arg::Gds(isc_obj_in_use) << Arg::Str("DATABASE"));
}

static void raiseObjectInUseError(const string& obj_type, const string& obj_name)
{
	string name;
	name.printf("%s \"%s\"", obj_type.c_str(), obj_name.c_str());

	ERR_post(Arg::Gds(isc_no_meta_update) <<
			 Arg::Gds(isc_obj_in_use) << Arg::Str(name));
}

static void raiseRelationInUseError(const jrd_rel* relation)
{
	const string obj_type =
		relation->isView() ? "VIEW" : "TABLE";
	const string obj_name = relation->rel_name.c_str();

	raiseObjectInUseError(obj_type, obj_name);
}

static void raiseRoutineInUseError(const Routine* routine, const QualifiedName& name)
{
	const string obj_type =
		(routine->getObjectType() == obj_udf) ? "FUNCTION" : "PROCEDURE";
	const string obj_name = routine->getName().toString();

	raiseObjectInUseError(obj_type, obj_name.hasData() ? obj_name : name.toString());
}

static void raiseTooManyVersionsError(const int obj_type, const string& obj_name)
{
	ERR_post(Arg::Gds(isc_no_meta_update) <<
			 Arg::Gds(getErrorCodeByObjectType(obj_type)) << Arg::Str(obj_name) <<
			 Arg::Gds(isc_version_err));
}

void Jrd::ProtectRelations::relLock::takeLock(thread_db* tdbb, jrd_tra* transaction)
{
	m_lock = RLCK_transaction_relation_lock(tdbb, transaction, m_relation);

	m_release = (m_lock->lck_logical == LCK_none);

	bool inUse = false;

	if (!m_release)
	{
		if ((m_lock->lck_logical < LCK_PR) &&
			!LCK_convert(tdbb, m_lock, LCK_PR, transaction->getLockWait()))
		{
			inUse = true;
		}
	}
	else
	{
		if (!LCK_lock(tdbb, m_lock, LCK_PR, transaction->getLockWait()))
			inUse = true;
	}

	if (inUse)
		raiseRelationInUseError(m_relation);
}


void Jrd::ProtectRelations::relLock::releaseLock(thread_db* tdbb, jrd_tra* transaction)
{
	if (!m_release)
		return;

	vec<Lock*>* vector = transaction->tra_relation_locks;
	if (vector)
	{
		vec<Lock*>::iterator lock = vector->begin();
		for (ULONG i = 0; i < vector->count(); ++i, ++lock)
		{
			if (*lock == m_lock)
			{
				LCK_release(tdbb, m_lock);
				*lock = NULL;
				break;
			}
		}
	}
}

static const UCHAR nonnull_validation_blr[] =
{
	blr_version5,
	blr_not,
	blr_missing,
	blr_fid, 0, 0, 0,
	blr_eoc
};

typedef bool (*dfw_task_routine) (thread_db*, SSHORT, DeferredWork*, jrd_tra*);
struct deferred_task
{
	enum dfw_t task_type;
	dfw_task_routine task_routine;
};

namespace
{
	template <typename Self, typename T, int objType,
		T* (*lookupById)(thread_db*, USHORT, bool, bool, USHORT),
		T* (*lookupByName)(Jrd::thread_db*, const QualifiedName&, bool),
		T* (*loadById)(thread_db*, USHORT, bool, USHORT)
	>
	class RoutineManager
	{
	public:
		// Create a new routine.
		static bool createRoutine(thread_db* tdbb, SSHORT phase, DeferredWork* work,
			jrd_tra* transaction)
		{
			SET_TDBB(tdbb);

			switch (phase)
			{
				case 1:
				case 2:
					return true;

				case 3:
				{
					const bool compile = !work->findArg(dfw_arg_check_blr);
					getDependencies(work, compile, transaction);

					T* routine = lookupByName(tdbb,
						QualifiedName(work->dfw_name, work->dfw_package), compile);

					if (!routine)
						return false;

					break;
				}
			}

			return false;
		}

		// Perform required actions when modifying a routine.
		static bool modifyRoutine(thread_db* tdbb, SSHORT phase, DeferredWork* work,
			jrd_tra* transaction)
		{
			SET_TDBB(tdbb);
			const QualifiedName name(work->dfw_name, work->dfw_package);
			Routine* routine;

			switch (phase)
			{
				case 0:
					routine = lookupById(tdbb, work->dfw_id, false, true, 0);
					if (!routine)
						return false;

					if (routine->existenceLock)
						LCK_convert(tdbb, routine->existenceLock, LCK_SR, transaction->getLockWait());

					return false;

				case 1:
				case 2:
					return true;

				case 3:
					routine = lookupById(tdbb, work->dfw_id, false, true, 0);
					if (!routine)
						return false;

					if (routine->existenceLock)
					{
						// Let routine be deleted if only this transaction is using it

						if (!LCK_convert(tdbb, routine->existenceLock, LCK_EX,
								transaction->getLockWait()))
						{
							raiseRoutineInUseError(routine, name);
						}
					}

					// If we are in a multi-client server, someone else may have marked
					// routine obsolete. Unmark and we will remark it later.

					routine->flags &= ~Routine::FLAG_OBSOLETE;
					return true;

				case 4:
				{
					routine = lookupById(tdbb, work->dfw_id, false, true, 0);
					if (!routine)
						return false;

					// Do not allow to modify routine used by user requests
					if (routine->isUsed() && MET_routine_in_use(tdbb, routine))
					{
						///raiseRoutineInUseError(routine, name);
						gds__log("Modifying %s %s which is currently in use by active user requests",
								 Self::getTypeStr(), name.toString().c_str());

						USHORT alterCount = routine->alterCount;

						if (alterCount > Routine::MAX_ALTER_COUNT)
							raiseTooManyVersionsError(routine->getObjectType(), work->dfw_name);

						if (routine->existenceLock)
							LCK_release(tdbb, routine->existenceLock);

						Self::clearId(tdbb->getAttachment(), routine->getId());

						if (!(routine = lookupById(tdbb, work->dfw_id, false,
								true, Routine::FLAG_BEING_ALTERED)))
						{
							return false;
						}

						routine->alterCount = ++alterCount;
					}

					routine->flags |= Routine::FLAG_BEING_ALTERED;

					if (routine->getStatement())
					{
						if (routine->getStatement()->isActive())
							raiseRoutineInUseError(routine, name);

						// release the request

						routine->releaseStatement(tdbb);
					}

					// delete dependency lists

					if (work->dfw_package.isEmpty())
						MET_delete_dependencies(tdbb, work->dfw_name, objType, transaction);

					/* the routine has just been scanned by lookupById
					   and its Routine::FLAG_SCANNED flag is set. We are going to reread it
					   from file (create all new dependencies) and do not want this
					   flag to be set. That is why we do not add Routine::FLAG_OBSOLETE and
					   Routine::FLAG_BEING_ALTERED flags, we set only these two flags
					 */
					routine->flags = (Routine::FLAG_OBSOLETE | Routine::FLAG_BEING_ALTERED);

					if (routine->existenceLock)
						LCK_release(tdbb, routine->existenceLock);

					// remove routine from cache

					routine->remove(tdbb);

					// Now handle the new definition
					bool compile = !work->findArg(dfw_arg_check_blr);
					getDependencies(work, compile, transaction);

					routine->flags &= ~(Routine::FLAG_OBSOLETE | Routine::FLAG_BEING_ALTERED);

					return true;
				}

				case 5:
					if (work->findArg(dfw_arg_check_blr))
					{
						SSHORT validBlr = FALSE;

						Jrd::Attachment* attachment = tdbb->getAttachment();
						MemoryPool* newPool = attachment->createPool();
						try
						{
							Jrd::ContextPoolHolder context(tdbb, newPool);

							// compile the routine to know if the BLR is still valid
							if (loadById(tdbb, work->dfw_id, false, 0))
								validBlr = TRUE;
						}
						catch (const Firebird::Exception&)
						{
							fb_utils::init_status(tdbb->tdbb_status_vector);
						}

						attachment->deletePool(newPool);

						Self::validate(tdbb, transaction, work, validBlr);
					}
					return true;

				case 6:
					Self::checkOutParamDependencies(tdbb, work, transaction);
					break;
			}

			return false;
		}

		// Check if it is allowed to delete a routine, and if so, clean up after it.
		static bool deleteRoutine(thread_db* tdbb, SSHORT phase, DeferredWork* work,
			jrd_tra* transaction)
		{
			SET_TDBB(tdbb);
			const QualifiedName name(work->dfw_name, work->dfw_package);
			T* routine;

			switch (phase)
			{
				case 0:
					routine = lookupById(tdbb, work->dfw_id, false, true, 0);
					if (!routine)
						return false;

					if (routine->existenceLock)
						LCK_convert(tdbb, routine->existenceLock, LCK_SR, transaction->getLockWait());

					return false;

				case 1:
					check_dependencies(tdbb, work->dfw_name.c_str(), NULL, work->dfw_package.c_str(),
						objType, transaction);
					return true;

				case 2:
					routine = lookupById(tdbb, work->dfw_id, false, true, 0);
					if (!routine)
						return false;

					if (routine->existenceLock)
					{
						if (!LCK_convert(tdbb, routine->existenceLock, LCK_EX,
								transaction->getLockWait()))
						{
							raiseRoutineInUseError(routine, name);
						}
					}

					// If we are in a multi-client server, someone else may have marked
					// routine obsolete. Unmark and we will remark it later.

					routine->flags &= ~Routine::FLAG_OBSOLETE;
					return true;

				case 3:
					return true;

				case 4:
				{
					routine = lookupById(tdbb, work->dfw_id, true, true, 0);
					if (!routine)
						return false;

					// Do not allow to drop routine used by user requests
					if (routine->isUsed() && MET_routine_in_use(tdbb, routine))
					{
						///raiseRoutineInUseError(routine, name);
						gds__log("Deleting %s %s which is currently in use by active user requests",
							Self::getTypeStr(), name.toString().c_str());

						if (work->dfw_package.isEmpty())
							MET_delete_dependencies(tdbb, work->dfw_name, objType, transaction);

						if (routine->existenceLock)
							LCK_release(tdbb, routine->existenceLock);

						Self::clearId(tdbb->getAttachment(), routine->getId());
						return false;
					}

					const USHORT old_flags = routine->flags;
					routine->flags |= Routine::FLAG_OBSOLETE;

					if (routine->getStatement())
					{
						if (routine->getStatement()->isActive())
						{
							routine->flags = old_flags;
							raiseRoutineInUseError(routine, name);
						}

						routine->releaseStatement(tdbb);
					}

					// delete dependency lists

					if (work->dfw_package.isEmpty())
						MET_delete_dependencies(tdbb, work->dfw_name, objType, transaction);

					if (routine->existenceLock)
						LCK_release(tdbb, routine->existenceLock);

					break;
				}
			}	// switch

			return false;
		}

	private:
		// Get relations and fields on which this routine depends, either when it's being
		// created or when it's modified.
		static void getDependencies(DeferredWork* work, bool compile, jrd_tra* transaction)
		{
			thread_db* tdbb = JRD_get_thread_data();
			Jrd::Attachment* attachment = tdbb->getAttachment();

			if (compile)
				compile = !tdbb->getAttachment()->isGbak();

			bid blobId;
			blobId.clear();
			Routine* routine = Self::lookupBlobId(tdbb, work, blobId, compile);

#ifdef DEV_BUILD
			MET_verify_cache(tdbb);
#endif

			// get any dependencies now by parsing the blr

			if (routine && !blobId.isEmpty())
			{
				JrdStatement* statement = NULL;
				// Nickolay Samofatov: allocate statement memory pool...
				MemoryPool* new_pool = attachment->createPool();
				// block is used to ensure MET_verify_cache
				// works in not deleted context
				{
					Jrd::ContextPoolHolder context(tdbb, new_pool);

					const MetaName depName(work->dfw_package.isEmpty() ?
						MetaName(work->dfw_name) : work->dfw_package);
					MET_get_dependencies(tdbb, NULL, NULL, 0, NULL, &blobId,
						(compile ? &statement : NULL),
						NULL, depName,
						(work->dfw_package.isEmpty() ? objType : obj_package_body),
						0, transaction);

					if (statement)
						statement->release(tdbb);
					else
						attachment->deletePool(new_pool);
				}

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

	class FunctionManager : public RoutineManager<FunctionManager, Function, obj_udf,
		Function::lookup, Function::lookup, Function::loadMetadata>
	{
	public:
		static const char* const getTypeStr()
		{
			return "function";
		}

		static void clearId(Jrd::Attachment* attachment, USHORT id)
		{
			attachment->att_functions[id] = NULL;
		}

		static Routine* lookupBlobId(thread_db* tdbb, DeferredWork* work, bid& blobId, bool compile);
		static void validate(thread_db* tdbb, jrd_tra* transaction, DeferredWork* work,
			SSHORT validBlr);
		static void checkOutParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction);
	};

	class ProcedureManager : public RoutineManager<ProcedureManager, jrd_prc, obj_procedure,
		MET_lookup_procedure_id, MET_lookup_procedure, MET_procedure>
	{
	public:
		static const char* const getTypeStr()
		{
			return "procedure";
		}

		static void clearId(Jrd::Attachment* attachment, USHORT id)
		{
			attachment->att_procedures[id] = NULL;
		}

		static Routine* lookupBlobId(thread_db* tdbb, DeferredWork* work, bid& blobId, bool compile);
		static void validate(thread_db* tdbb, jrd_tra* transaction, DeferredWork* work,
			SSHORT validBlr);
		static void checkOutParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction);
	};

	// These methods cannot be defined inline, because GPRE generates wrong code.

	Routine* FunctionManager::lookupBlobId(thread_db* tdbb, DeferredWork* work, bid& blobId,
		bool compile)
	{
		Jrd::Attachment* attachment = tdbb->getAttachment();
		AutoCacheRequest handle(tdbb, irq_c_fun_dpd, IRQ_REQUESTS);
		Routine* routine = NULL;

		FOR(REQUEST_HANDLE handle)
			X IN RDB$FUNCTIONS WITH
				X.RDB$FUNCTION_NAME EQ work->dfw_name.c_str() AND
				X.RDB$PACKAGE_NAME EQUIV NULLIF(work->dfw_package.c_str(), '')
		{
			blobId = X.RDB$FUNCTION_BLR;
			routine = Function::lookup(tdbb,
				QualifiedName(work->dfw_name, work->dfw_package), !compile);
		}
		END_FOR

		return routine;
	}

	void FunctionManager::validate(thread_db* tdbb, jrd_tra* transaction, DeferredWork* work,
		SSHORT validBlr)
	{
		Jrd::Attachment* attachment = tdbb->getAttachment();
		AutoCacheRequest request(tdbb, irq_fun_validate, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			FUN IN RDB$FUNCTIONS
			WITH FUN.RDB$FUNCTION_ID EQ work->dfw_id AND
				 FUN.RDB$FUNCTION_BLR NOT MISSING
		{
			MODIFY FUN USING
				FUN.RDB$VALID_BLR = validBlr;
				FUN.RDB$VALID_BLR.NULL = FALSE;
			END_MODIFY
		}
		END_FOR
	}

	void FunctionManager::checkOutParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction)
	{
		// Do nothing, as function output is unnamed.
	}

	Routine* ProcedureManager::lookupBlobId(thread_db* tdbb, DeferredWork* work, bid& blobId,
		bool compile)
	{
		Jrd::Attachment* attachment = tdbb->getAttachment();
		AutoCacheRequest handle(tdbb, irq_c_prc_dpd, IRQ_REQUESTS);
		Routine* routine = NULL;

		FOR(REQUEST_HANDLE handle)
			X IN RDB$PROCEDURES WITH
				X.RDB$PROCEDURE_NAME EQ work->dfw_name.c_str() AND
				X.RDB$PACKAGE_NAME EQUIV NULLIF(work->dfw_package.c_str(), '')
		{
			blobId = X.RDB$PROCEDURE_BLR;
			routine = MET_lookup_procedure(tdbb,
				QualifiedName(work->dfw_name, work->dfw_package), !compile);
		}
		END_FOR

		return routine;
	}

	void ProcedureManager::validate(thread_db* tdbb, jrd_tra* transaction, DeferredWork* work,
		SSHORT validBlr)
	{
		Jrd::Attachment* attachment = tdbb->getAttachment();
		AutoCacheRequest request(tdbb, irq_prc_validate, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			PRC IN RDB$PROCEDURES
			WITH PRC.RDB$PROCEDURE_ID EQ work->dfw_id AND
				 PRC.RDB$PROCEDURE_BLR NOT MISSING
		{
			MODIFY PRC USING
				PRC.RDB$VALID_BLR = validBlr;
				PRC.RDB$VALID_BLR.NULL = FALSE;
			END_MODIFY
		}
		END_FOR
	}

	void ProcedureManager::checkOutParamDependencies(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction)
	{
		Jrd::Attachment* attachment = tdbb->getAttachment();
		AutoCacheRequest handle(tdbb, irq_out_proc_param_dep, IRQ_REQUESTS);
		ObjectsArray<string> names;
		int depCount = 0;

		FOR (REQUEST_HANDLE handle)
			DEP IN RDB$DEPENDENCIES
			WITH DEP.RDB$DEPENDED_ON_NAME EQ work->dfw_name.c_str() AND
				 DEP.RDB$PACKAGE_NAME EQUIV NULLIF(work->dfw_package.c_str(), '') AND
				 DEP.RDB$DEPENDED_ON_TYPE = obj_procedure AND
				 NOT DEP.RDB$FIELD_NAME MISSING AND
				 NOT ANY PP IN RDB$PROCEDURE_PARAMETERS
						 WITH PP.RDB$PROCEDURE_NAME EQ DEP.RDB$DEPENDED_ON_NAME AND
							  PP.RDB$PACKAGE_NAME EQUIV DEP.RDB$PACKAGE_NAME AND
							  PP.RDB$PARAMETER_NAME EQ DEP.RDB$FIELD_NAME AND
							  PP.RDB$PARAMETER_TYPE EQ 1
		{
			// If the found object is also being deleted, there's no dependency

			if (!find_depend_in_dfw(tdbb, DEP.RDB$DEPENDENT_NAME, DEP.RDB$DEPENDENT_TYPE, 0, transaction))
			{
				string& name = names.add();
				name.printf("%s.%s", work->dfw_name.c_str(), DEP.RDB$FIELD_NAME);

				++depCount;
			}
		}
		END_FOR

		if (names.hasData())
		{
			Arg::StatusVector status;
			status << Arg::Gds(isc_no_meta_update) << Arg::Gds(isc_no_delete);

			for (auto& name : names)
				status << Arg::Gds(isc_parameter_name) << Arg::Str(name);

			status << Arg::Gds(isc_dependency) << Arg::Num(depCount);

			ERR_post(status);
		}
	}
} // namespace

static const deferred_task task_table[] =
{
	{ dfw_add_file, add_file },
	{ dfw_add_shadow, add_shadow },
	{ dfw_delete_index, modify_index },
	{ dfw_delete_expression_index, modify_index },
	{ dfw_delete_rfr, delete_rfr },
	{ dfw_delete_relation, delete_relation },
	{ dfw_delete_shadow, delete_shadow },
	{ dfw_delete_shadow_nodelete, delete_shadow },
	{ dfw_create_field, create_field },
	{ dfw_delete_field, delete_field },
	{ dfw_modify_field, modify_field },
	{ dfw_delete_global, delete_global },
	{ dfw_create_relation, create_relation },
	{ dfw_update_format, make_version },
	{ dfw_scan_relation, scan_relation },
	{ dfw_compute_security, compute_security },
	{ dfw_create_index, modify_index },
	{ dfw_create_expression_index, modify_index },
	{ dfw_grant, grant_privileges },
	{ dfw_create_trigger, create_trigger },
	{ dfw_delete_trigger, delete_trigger },
	{ dfw_modify_trigger, modify_trigger },
	{ dfw_drop_package_header, drop_package_header },	// packages should be before procedures
	{ dfw_drop_package_body, drop_package_body },		// packages should be before procedures
	{ dfw_create_procedure, ProcedureManager::createRoutine },
	{ dfw_create_function, FunctionManager::createRoutine },
	{ dfw_delete_procedure, ProcedureManager::deleteRoutine },
	{ dfw_delete_function, FunctionManager::deleteRoutine },
	{ dfw_modify_procedure, ProcedureManager::modifyRoutine },
	{ dfw_modify_function, FunctionManager::modifyRoutine },
	{ dfw_delete_prm, delete_parameter },
	{ dfw_create_collation, create_collation },
	{ dfw_delete_collation, delete_collation },
	{ dfw_delete_exception, delete_exception },
	{ dfw_set_generator, set_generator },
	{ dfw_delete_generator, delete_generator },
	{ dfw_add_difference, add_difference },
	{ dfw_delete_difference, delete_difference },
	{ dfw_begin_backup, begin_backup },
	{ dfw_end_backup, end_backup },
	{ dfw_user_management, user_management },
	{ dfw_check_not_null, check_not_null },
	{ dfw_store_view_context_type, store_view_context_type },
	{ dfw_db_crypt, db_crypt },
	{ dfw_set_linger, set_linger },
	{ dfw_clear_cache, clear_cache },
	{ dfw_change_repl_state, change_repl_state },
	{ dfw_null, NULL }
};


USHORT DFW_assign_index_type(thread_db* tdbb, const MetaName& name, SSHORT field_type,
	SSHORT ttype)
{
/**************************************
 *
 *	D F W _ a s s i g n _ i n d e x _ t y p e
 *
 **************************************
 *
 * Functional description
 *	Define the index segment type based
 * 	on the field's type and subtype.
 *
 **************************************/
	SET_TDBB(tdbb);

	if (field_type == dtype_varying || field_type == dtype_cstring || field_type == dtype_text)
	{
	    switch (ttype)
	    {
		case ttype_none:
			return idx_string;
		case ttype_binary:
			return idx_byte_array;
		case ttype_metadata:
			return idx_metadata;
		case ttype_ascii:
			return idx_string;
		}

		// Dynamic text cannot occur here as this is for an on-disk
		// index, which must be bound to a text type.

		fb_assert(ttype != ttype_dynamic);

		if (INTL_defined_type(tdbb, ttype))
			return INTL_TEXT_TO_INDEX(ttype);

		ERR_post_nothrow(Arg::Gds(isc_no_meta_update) <<
						 Arg::Gds(isc_random) << Arg::Str(name));
		INTL_texttype_lookup(tdbb, ttype);	// should punt
		ERR_punt(); // if INTL_texttype_lookup hasn't punt
	}

	switch (field_type)
	{
	case dtype_timestamp:
		return idx_timestamp;
	case dtype_timestamp_tz:
		return idx_timestamp_tz;
	case dtype_sql_date:
		return idx_sql_date;
	case dtype_sql_time:
		return idx_sql_time;
	case dtype_sql_time_tz:
		return idx_sql_time_tz;
	// idx_numeric2 used for 64-bit Integer support
	case dtype_int64:
		return idx_numeric2;
	case dtype_boolean:
		return idx_boolean;
	case dtype_dec64:
	case dtype_dec128:
	case dtype_int128:
		return idx_decimal;
	default:
		return idx_numeric;
	}
}


void DFW_delete_deferred( jrd_tra* transaction, SavNumber sav_number)
{
/**************************************
 *
 *	D F W _ d e l e t e _ d e f e r r e d
 *
 **************************************
 *
 * Functional description
 *	Get rid of work deferred that was to be done at
 *	COMMIT time as the statement has been rolled back.
 *
 *	if (sav_number == -1), then  remove all entries.
 *
 **************************************/

	// If there is no deferred work, just return

	if (!transaction->tra_deferred_job) {
		return;
	}

	// Remove deferred work and events which are to be rolled back

	if (sav_number == -1)
	{
		DeferredWork* work;
		while ((work = transaction->tra_deferred_job->work))
		{
			delete work;
		}
		transaction->tra_flags &= ~TRA_deferred_meta;
		return;
	}

	DfwSavePoint* h = transaction->tra_deferred_job->hash.lookup(sav_number);
	if (!h)
	{
		return;
	}

	for (DfwHash::iterator i(h->hash); i.hasData();)
	{
		DeferredWork* work(i);
		++i;
		delete work;
	}
}


// Get (by reference) the array of IDs present in a DeferredWork.
SortedArray<int>& DFW_get_ids(DeferredWork* work)
{
	return work->dfw_ids;
}


void DFW_merge_work(jrd_tra* transaction, SavNumber old_sav_number, SavNumber new_sav_number)
{
/**************************************
 *
 *	D F W _ m e r g e _ w o r k
 *
 **************************************
 *
 * Functional description
 *	Merge the deferred work with the previous level.  This will
 *	be called only if there is a previous level.
 *
 **************************************/

	// If there is no deferred work, just return

	DeferredJob *job = transaction->tra_deferred_job;
	if (! job)
		return;

	// Check to see if work is already posted

	DfwSavePoint* oldSp = job->hash.lookup(old_sav_number);
	if (!oldSp)
		return;

	DfwSavePoint* newSp = job->hash.lookup(new_sav_number);

	// Decrement the save point number in the deferred block
	// i.e. merge with the previous level.

	for (DfwHash::iterator itr(oldSp->hash); itr.hasData();)
	{
		if (! newSp)
		{
			newSp = FB_NEW_POOL(*transaction->tra_pool) DfwSavePoint(new_sav_number);
			job->hash.add(newSp);
		}

		DeferredWork* work(itr);
		++itr;
		oldSp->hash.remove(*work); // After ++itr
		work->dfw_sav_number = new_sav_number;

		DeferredWork* newWork = newSp->hash.lookup(*work);

		if (!newWork)
			newSp->hash.add(work);
		else
		{
			SortedArray<int>& workIds = work->dfw_ids;

			for (SortedArray<int>::iterator itr2(workIds.begin()); itr2 != workIds.end(); ++itr2)
			{
				int n = *itr2;
				if (!newWork->dfw_ids.exist(n))
					newWork->dfw_ids.add(n);
			}

			newWork->dfw_count += work->dfw_count;
			delete work;
		}
	}

	job->hash.remove(old_sav_number);
	delete oldSp;
}


void DFW_perform_system_work(thread_db* tdbb)
{
/**************************************
 *
 *	D F W _ p e r f o r m _ s y s t e m _ w o r k
 *
 **************************************
 *
 * Functional description
 *	Flush out the work left to be done in the
 *	system transaction.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	DFW_perform_work(tdbb, attachment->getSysTransaction());
}


void DFW_perform_work(thread_db* tdbb, jrd_tra* transaction)
{
/**************************************
 *
 *	D F W _ p e r f o r m _ w o r k
 *
 **************************************
 *
 * Functional description
 *	Do work deferred to COMMIT time 'cause that time has
 *	come.
 *
 **************************************/

	// If no deferred work or it's all deferred event posting don't bother

	if (!transaction->tra_deferred_job || !(transaction->tra_flags & TRA_deferred_meta))
	{
		return;
	}

	SET_TDBB(tdbb);
	Jrd::ContextPoolHolder context(tdbb, transaction->tra_pool);

	/* Loop for as long as any of the deferred work routines says that it has
	more to do.  A deferred work routine should be able to deal with any
	value of phase, either to say that it wants to be called again in the
	next phase (by returning true) or that it has nothing more to do in this
	or later phases (by returning false). By convention, phase 0 has been
	designated as the cleanup phase. If any non-zero phase punts, then phase 0
	is executed for all deferred work blocks to cleanup work-in-progress. */

	bool dump_shadow = false;
	SSHORT phase = 1;
	bool more;
	FbLocalStatus err_status;

	do
	{
		more = false;
		try {
			const auto flags = (TDBB_dont_post_dfw | TDBB_use_db_page_space |
				(phase == 0 ? TDBB_dfw_cleanup : 0));
			AutoSetRestoreFlag<ULONG> dfwFlags(&tdbb->tdbb_flags, flags, true);

			for (const deferred_task* task = task_table; task->task_type != dfw_null; ++task)
			{
				for (DeferredWork* work = transaction->tra_deferred_job->work;
					work; work = work->getNext())
				{
					if (work->dfw_type == task->task_type)
					{
						if (work->dfw_type == dfw_add_shadow)
						{
							dump_shadow = true;
						}
						if ((*task->task_routine)(tdbb, phase, work, transaction))
						{
							more = true;
						}
					}
				}
			}

			if (!phase)
			{
				fb_utils::copyStatus(tdbb->tdbb_status_vector, &err_status);
				ERR_punt();
			}

			++phase;
		}
		catch (const Firebird::Exception& ex)
		{
			// Do any necessary cleanup
			if (!phase)
			{
				ex.stuffException(tdbb->tdbb_status_vector);
				ERR_punt();
			}
			else
				ex.stuffException(&err_status);

			phase = 0;
			more = true;
		}

	} while (more);

	// Remove deferred work blocks so that system transaction and
	// commit retaining transactions don't re-execute them. Leave
	// events to be posted after commit

	for (DeferredWork* itr = transaction->tra_deferred_job->work; itr;)
	{
		DeferredWork* work = itr;
		itr = itr->getNext();

		switch (work->dfw_type)
		{
		case dfw_post_event:
		case dfw_delete_shadow:
			break;

		default:
			delete work;
			break;
		}
	}

	transaction->tra_flags &= ~TRA_deferred_meta;

	if (dump_shadow) {
		SDW_dump_pages(tdbb);
	}
}


void DFW_perform_post_commit_work(jrd_tra* transaction)
{
/**************************************
 *
 *	D F W _ p e r f o r m _ p o s t _ c o m m i t _ w o r k
 *
 **************************************
 *
 * Functional description
 *	Perform any post commit work
 *	1. Post any pending events.
 *	2. Unlink shadow files for dropped shadows
 *
 *	Then, delete it from chain of pending work.
 *
 **************************************/

	if (!transaction->tra_deferred_job)
		return;

	bool pending_events = false;

	Database* dbb = GET_DBB();

	for (DeferredWork* itr = transaction->tra_deferred_job->work; itr;)
	{
		DeferredWork* work = itr;
		itr = itr->getNext();

		switch (work->dfw_type)
		{
		case dfw_post_event:
			EventManager::init(transaction->tra_attachment);

			dbb->eventManager()->postEvent(work->dfw_name.length(),
										   work->dfw_name.c_str(),
										   work->dfw_count);

			delete work;
			pending_events = true;
			break;
		case dfw_delete_shadow:
			if (work->dfw_name.hasData())
				unlink(work->dfw_name.c_str());
			delete work;
			break;
		default:
			break;
		}
	}

	if (pending_events)
	{
		dbb->eventManager()->deliverEvents();
	}
}


DeferredWork* DFW_post_system_work(thread_db* tdbb, enum dfw_t type, const dsc* desc, USHORT id)
{
/**************************************
 *
 *	D F W _ p o s t _ s y s t e m _ w o r k
 *
 **************************************
 *
 * Functional description
 *	Post work to be done in the context of system transaction.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	return DFW_post_work(attachment->getSysTransaction(), type, desc, id, "");
}


DeferredWork* DFW_post_work(jrd_tra* transaction, enum dfw_t type, const dsc* desc, USHORT id,
	const MetaName& package)
{
/**************************************
 *
 *	D F W _ p o s t _ w o r k
 *
 **************************************
 *
 * Functional description
 *	Post work to be deferred to commit time.
 *
 **************************************/

	return DFW_post_work(transaction, type, get_string(desc), id, package);
}


DeferredWork* DFW_post_work(jrd_tra* transaction, enum dfw_t type, const string& name, USHORT id,
	const MetaName& package)
{
/**************************************
 *
 *	D F W _ p o s t _ w o r k
 *
 **************************************
 *
 * Functional description
 *	Post work to be deferred to commit time.
 *
 **************************************/

	// get the current save point number

	const SavNumber sav_number = transaction->tra_save_point ?
		transaction->tra_save_point->getNumber() : 0;

	// initialize transaction if needed

	DeferredJob *job = transaction->tra_deferred_job;
	if (! job)
	{
		transaction->tra_deferred_job = job = FB_NEW_POOL(*transaction->tra_pool) DeferredJob;
	}

	// Check to see if work is already posted

	DfwSavePoint* sp = job->hash.lookup(sav_number);
	if (! sp)
	{
		sp = FB_NEW_POOL(*transaction->tra_pool) DfwSavePoint(sav_number);
		job->hash.add(sp);
	}

	DeferredWork tmp(AutoStorage::getAutoMemoryPool(), 0, type, id, sav_number, name, package);
	DeferredWork* work = sp->hash.lookup(tmp);
	if (work)
	{
		work->dfw_count++;
		return work;
	}

	// Not already posted, so do so now.

	work = FB_NEW_POOL(*transaction->tra_pool)
		DeferredWork(*transaction->tra_pool, &(job->end), type, id, sav_number, name, package);
	job->end = work->getNextPtr();
	fb_assert(!(*job->end));
	sp->hash.add(work);

	switch (type)
	{
	case dfw_user_management:
	case dfw_set_generator:
		transaction->tra_flags |= TRA_deferred_meta;
		// fall down ...
	case dfw_post_event:
		if (transaction->tra_save_point)
			transaction->tra_save_point->forceDeferredWork();
		break;
	default:
		transaction->tra_flags |= TRA_deferred_meta;
		break;
	}

	return work;
}


DeferredWork* DFW_post_work_arg( jrd_tra* transaction, DeferredWork* work, const dsc* desc,
	USHORT id)
{
/**************************************
 *
 *	D F W _ p o s t _ w o r k _ a r g
 *
 **************************************
 *
 * Functional description
 *	Post an argument for work to be deferred to commit time.
 *
 **************************************/
	return DFW_post_work_arg(transaction, work, desc, id, work->dfw_type);
}


DeferredWork* DFW_post_work_arg( jrd_tra* transaction, DeferredWork* work, const dsc* desc,
	USHORT id, Jrd::dfw_t type)
{
/**************************************
 *
 *	D F W _ p o s t _ w o r k _ a r g
 *
 **************************************
 *
 * Functional description
 *	Post an argument for work to be deferred to commit time.
 *
 **************************************/
	const Firebird::string name = get_string(desc);

	DeferredWork* arg = work->findArg(type);

	if (! arg)
	{
		arg = FB_NEW_POOL(*transaction->tra_pool)
			DeferredWork(*transaction->tra_pool, 0, type, id, 0, name, "");
		work->dfw_args.add(arg);
	}

	return arg;
}


void DFW_update_index(const TEXT* name, USHORT id, const SelectivityList& selectivity,
	jrd_tra* transaction)
{
/**************************************
 *
 *	D F W _ u p d a t e _ i n d e x
 *
 **************************************
 *
 * Functional description
 *	Update information in the index relation after creation
 *	of the index.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	AutoCacheRequest request(tdbb, irq_m_index_seg, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		SEG IN RDB$INDEX_SEGMENTS WITH SEG.RDB$INDEX_NAME EQ name
			SORTED BY SEG.RDB$FIELD_POSITION
	{
		MODIFY SEG USING
			SEG.RDB$STATISTICS = selectivity[SEG.RDB$FIELD_POSITION];
		END_MODIFY
	}
	END_FOR

	request.reset(tdbb, irq_m_index, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES WITH IDX.RDB$INDEX_NAME EQ name
	{
		MODIFY IDX USING
			IDX.RDB$INDEX_ID = id + 1;
			IDX.RDB$STATISTICS = selectivity.back();
		END_MODIFY
	}
	END_FOR
}


static bool add_file(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	a d d _ f i l e
 *
 **************************************
 *
 * Functional description
 *	Add a file to a database.
 *	This file could be a regular database
 *	file or a shadow file.  Either way we
 *	require exclusive access to the database.
 *
 **************************************/
	USHORT section, shadow_number;
	SLONG start, min_start;

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 0:
		CCH_release_exclusive(tdbb);
		return false;

	case 1:
	case 2:
		return true;

	case 3:
		if (!CCH_exclusive(tdbb, LCK_EX, WAIT_PERIOD, NULL))
			raiseDatabaseInUseError(true);
		return true;

	case 4:
		CCH_flush(tdbb, FLUSH_FINI, 0);
		start = PageSpace::maxAlloc(dbb) + 1;
		AutoRequest handle;
		AutoRequest handle2;

		// Check the file name for node name. This has already
		// been done for shadows in add_shadow()

		if (work->dfw_type != dfw_add_shadow)
			check_filename(tdbb, work->dfw_name, true);

		// User transaction may be safely used instead of system, cause
		// we requested and got exclusive database access. AP-2008.

		// get any files to extend into

		FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction) X IN RDB$FILES
			WITH X.RDB$FILE_NAME EQ work->dfw_name.c_str()
			// First expand the file name This has already been done
			// for shadows in add_shadow ())
			if (work->dfw_type != dfw_add_shadow)
			{
				MODIFY X USING
					ISC_expand_filename(X.RDB$FILE_NAME, 0,
						X.RDB$FILE_NAME, sizeof(X.RDB$FILE_NAME), false);
				END_MODIFY
			}

			// Check the previous file length
			FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE transaction)
				FIRST 1 Y IN RDB$FILES
					WITH Y.RDB$SHADOW_NUMBER EQ X.RDB$SHADOW_NUMBER
					AND Y.RDB$FILE_SEQUENCE NOT MISSING
					SORTED BY DESCENDING Y.RDB$FILE_SEQUENCE
			{
				if (!Y.RDB$FILE_START.NULL && !Y.RDB$FILE_LENGTH.NULL)
				{
					min_start = Y.RDB$FILE_START + (Y.RDB$FILE_LENGTH ? Y.RDB$FILE_LENGTH : 1);
					start = MAX(min_start, start);
				}
			}
			END_FOR

			// If there is no starting position specified, or if it is
			// too low a value, raise the error.
			if (X.RDB$FILE_START < start)
			{
				ERR_post(Arg::Gds(isc_file_starting_page_err) <<
					Arg::Str(X.RDB$FILE_NAME) << Arg::Num(start));
			}

			start = X.RDB$FILE_START;

			shadow_number = X.RDB$SHADOW_NUMBER;
			if ((shadow_number &&
				(section = SDW_add_file(tdbb, X.RDB$FILE_NAME, start, shadow_number))) ||
				(section = PAG_add_file(tdbb, X.RDB$FILE_NAME, start)))
			{
				MODIFY X USING
					X.RDB$FILE_SEQUENCE = section;
					X.RDB$FILE_START = start;
				END_MODIFY
			}
		END_FOR

		if (section)
		{
			handle.reset();
			section--;
			FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction) X IN RDB$FILES
				WITH X.RDB$FILE_SEQUENCE EQ section
					AND X.RDB$SHADOW_NUMBER EQ shadow_number
			{
				MODIFY X USING
					X.RDB$FILE_LENGTH = start - X.RDB$FILE_START;
				END_MODIFY
			}
			END_FOR
		}

		CCH_release_exclusive(tdbb);
		break;
	}

	return false;
}



static bool add_shadow(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	a d d _ s h a d o w
 *
 **************************************
 *
 * Functional description
 *	A file or files have been added for shadowing.
 *	Get all files for this particular shadow first
 *	in order of starting page, if specified, then
 *	in sequence order.
 *
 **************************************/

	AutoRequest handle;
	Shadow* shadow;
	USHORT sequence, add_sequence;
	bool finished;
	ULONG min_page;
	Firebird::PathName expanded_fname;

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 0:
		CCH_release_exclusive(tdbb);
		return false;

	case 1:
	case 2:
	case 3:
		return true;

	case 4:
		check_filename(tdbb, work->dfw_name, false);

		/* could have two cases:
		   1) this shadow has already been written to, so add this file using
		   the standard routine to extend a database
		   2) this file is part of a newly added shadow which has already been
		   fetched in totem and prepared for writing to, so just ignore it
		 */

		finished = false;
		handle.reset();
		FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
			F IN RDB$FILES
				WITH F.RDB$FILE_NAME EQ work->dfw_name.c_str()

			expanded_fname = F.RDB$FILE_NAME;
			ISC_expand_filename(expanded_fname, false);
			MODIFY F USING
				expanded_fname.copyTo(F.RDB$FILE_NAME, sizeof(F.RDB$FILE_NAME));
			END_MODIFY

			for (shadow = dbb->dbb_shadow; shadow; shadow = shadow->sdw_next)
			{
				if ((F.RDB$SHADOW_NUMBER == shadow->sdw_number) && !(shadow->sdw_flags & SDW_IGNORE))
				{
					if (F.RDB$FILE_FLAGS & FILE_shadow)
					{
						// This is the case of a bogus duplicate posted
						// work when we added a multi-file shadow
						finished = true;
					}
					else if (shadow->sdw_flags & (SDW_dumped))
					{
						/* Case of adding a file to a currently active
						 * shadow set.
						 * Note: as of 1995-January-31 there is
						 * no SQL syntax that supports this, but there
						 * may be GDML
						 */
						add_file(tdbb, 3, work, transaction);
						add_file(tdbb, 4, work, transaction);
						finished = true;
					}
					else
					{
						// We cannot add a file to a shadow that is still
						// in the process of being created.
						raiseDatabaseInUseError(false);
					}
					break;
				}
			}

		END_FOR

		if (finished)
			return false;

		// this file is part of a new shadow, so get all files for the shadow
		// in order of the starting page for the file

		// Note that for a multi-file shadow, we have several pieces of
		// work posted (one dfw_add_shadow for each file).  Rather than
		// trying to cancel the other pieces of work we ignore them
		// when they arrive in this routine.

		sequence = 0;
		min_page = 0;
		shadow = NULL;
		handle.reset();
		FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
			X IN RDB$FILES CROSS
				Y IN RDB$FILES
				OVER RDB$SHADOW_NUMBER
				WITH X.RDB$FILE_NAME EQ expanded_fname.c_str()
				SORTED BY Y.RDB$FILE_START
		{
			// for the first file, create a brand new shadow; for secondary
			// files that have a starting page specified, add a file
			if (!sequence)
				SDW_add(tdbb, Y.RDB$FILE_NAME, Y.RDB$SHADOW_NUMBER, Y.RDB$FILE_FLAGS);
			else if (Y.RDB$FILE_START)
			{
				if (!shadow)
				{
					for (shadow = dbb->dbb_shadow; shadow; shadow = shadow->sdw_next)
					{
						if ((Y.RDB$SHADOW_NUMBER == shadow->sdw_number) &&
							!(shadow->sdw_flags & SDW_IGNORE))
						{
								break;
						}
					}
				}

				if (!shadow)
					BUGCHECK(203);	// msg 203 shadow block not found for extend file

				min_page = MAX((min_page + 1), (ULONG) Y.RDB$FILE_START);
				add_sequence = SDW_add_file(tdbb, Y.RDB$FILE_NAME, min_page, Y.RDB$SHADOW_NUMBER);
			}

			// update the sequence number and bless the file entry as being good

			if (!sequence || (Y.RDB$FILE_START && add_sequence))
			{
				MODIFY Y
					Y.RDB$FILE_FLAGS |= FILE_shadow;
					Y.RDB$FILE_SEQUENCE = sequence;
					Y.RDB$FILE_START = min_page;
				END_MODIFY
				sequence++;
			}
		}
		END_FOR

		break;
	}

	return false;
}

static bool add_difference(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	a d d _ d i f f e r e n c e
 *
 **************************************
 *
 * Functional description
 *	Add backup difference file to the database
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			BackupManager::StateReadGuard stateGuard(tdbb);
			if (dbb->dbb_backup_manager->getState() != Ods::hdr_nbak_normal)
			{
				ERR_post(Arg::Gds(isc_no_meta_update) <<
						 Arg::Gds(isc_wrong_backup_state));
			}
			check_filename(tdbb, work->dfw_name, true);
			dbb->dbb_backup_manager->setDifference(tdbb, work->dfw_name.c_str());
		}
		break;
	}

	return false;
}


static bool delete_difference(thread_db* tdbb, SSHORT phase, DeferredWork*, jrd_tra*)
{
/**************************************
 *
 *	d e l e t e _ d i f f e r e n c e
 *
 **************************************
 *
 * Delete backup difference file for database
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			BackupManager::StateReadGuard stateGuard(tdbb);

			if (dbb->dbb_backup_manager->getState() != Ods::hdr_nbak_normal)
			{
				ERR_post(Arg::Gds(isc_no_meta_update) <<
						 Arg::Gds(isc_wrong_backup_state));
			}
			dbb->dbb_backup_manager->setDifference(tdbb, NULL);
		}
		break;
	}

	return false;
}

static bool begin_backup(thread_db* tdbb, SSHORT phase, DeferredWork*, jrd_tra*)
{
/**************************************
 *
 *	b e g i n _ b a c k u p
 *
 **************************************
 *
 * Begin backup storing changed pages in difference file
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		dbb->dbb_backup_manager->beginBackup(tdbb);
		break;
	}

	return false;
}

static bool end_backup(thread_db* tdbb, SSHORT phase, DeferredWork*, jrd_tra*)
{
/**************************************
 *
 *	e n d _ b a c k u p
 *
 **************************************
 *
 * End backup and merge difference file if neseccary
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		// End backup normally
		dbb->dbb_backup_manager->endBackup(tdbb, false);
		break;
	}

	return false;
}

static bool db_crypt(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	d b _ c r y p t
 *
 **************************************
 *
 * Encrypt database using plugin dfw_name or decrypt if dfw_name is empty.
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		dbb->dbb_crypto_manager->changeCryptState(tdbb, work->dfw_name);
		break;
	}

	return false;
}

static bool set_linger(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	s e t _ l i n g e r
 *
 **************************************
 *
 * Set linger interval in Database block.
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 1:
	case 2:
	case 3:
		return true;

	case 4:
		dbb->dbb_linger_seconds = atoi(work->dfw_name.c_str());		// number stored as string
		break;
	}

	return false;
}

static bool clear_cache(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	c l e a r _ c a c h e
 *
 **************************************
 *
 * Clear security names mapping cache
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		Mapping::clearCache(dbb->dbb_filename.c_str(), work->dfw_id);
		break;
	}

	return false;
}

static bool check_not_null(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	c h e c k _ n o t _ n u l l
 *
 **************************************
 *
 * Scan relation to detect NULLs in fields being changed to NOT NULL.
 *
 **************************************/

	SET_TDBB(tdbb);

	Jrd::Attachment* attachment = tdbb->getAttachment();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			jrd_rel* relation = MET_lookup_relation(tdbb, work->dfw_name);
			if (!relation || relation->rel_view_rse || work->dfw_ids.isEmpty())
				break;

			// Protect relation from modification
			ProtectRelations protectRelation(tdbb, transaction, relation);

			SortedArray<int> fields;
			AutoRequest handle;

			for (SortedArray<int>::iterator itr(work->dfw_ids.begin());
				 itr != work->dfw_ids.end();
				 ++itr)
			{
				FOR(REQUEST_HANDLE handle)
					RFL IN RDB$RELATION_FIELDS CROSS
					FLD IN RDB$FIELDS
					WITH RFL.RDB$RELATION_NAME EQ work->dfw_name.c_str() AND
						 FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE AND
						 RFL.RDB$FIELD_ID EQ *itr AND
						 (RFL.RDB$NULL_FLAG = TRUE OR FLD.RDB$NULL_FLAG = TRUE)
				{
					fields.add(RFL.RDB$FIELD_ID);
				}
				END_FOR
			}

			if (fields.hasData())
			{
				UCharBuffer blr;

				blr.add(blr_version5);
				blr.add(blr_begin);
				blr.add(blr_message);
				blr.add(1);	// message number
				blr.add(fields.getCount() & 0xFF);
				blr.add(fields.getCount() >> 8);

				for (FB_SIZE_T i = 0; i < fields.getCount(); ++i)
				{
					blr.add(blr_short);
					blr.add(0);
				}

				blr.add(blr_for);
				blr.add(blr_stall);
				blr.add(blr_rse);
				blr.add(1);
				blr.add(blr_rid);
				blr.add(relation->rel_id & 0xFF);
				blr.add(relation->rel_id >> 8);
				blr.add(0);	// stream
				blr.add(blr_boolean);

				for (FB_SIZE_T i = 0; i < fields.getCount(); ++i)
				{
					if (i != fields.getCount() - 1)
						blr.add(blr_or);

					blr.add(blr_missing);
					blr.add(blr_fid);
					blr.add(0);	// stream
					blr.add(USHORT(fields[i]) & 0xFF);
					blr.add(USHORT(fields[i]) >> 8);
				}

				blr.add(blr_end);

				blr.add(blr_send);
				blr.add(1);
				blr.add(blr_begin);

				for (FB_SIZE_T i = 0; i < fields.getCount(); ++i)
				{
					blr.add(blr_assignment);

					blr.add(blr_value_if);
					blr.add(blr_missing);
					blr.add(blr_fid);
					blr.add(0);	// stream
					blr.add(USHORT(fields[i]) & 0xFF);
					blr.add(USHORT(fields[i]) >> 8);

					blr.add(blr_literal);
					blr.add(blr_short);
					blr.add(0);
					blr.add(1);
					blr.add(0);

					blr.add(blr_literal);
					blr.add(blr_short);
					blr.add(0);
					blr.add(0);
					blr.add(0);

					blr.add(blr_parameter);
					blr.add(1);	// message number
					blr.add(i & 0xFF);
					blr.add(i >> 8);
				}

				blr.add(blr_end);

				blr.add(blr_send);
				blr.add(1);
				blr.add(blr_begin);

				for (FB_SIZE_T i = 0; i < fields.getCount(); ++i)
				{
					blr.add(blr_assignment);
					blr.add(blr_literal);
					blr.add(blr_short);
					blr.add(0);
					blr.add(0);
					blr.add(0);
					blr.add(blr_parameter);
					blr.add(1);	// message number
					blr.add(i & 0xFF);
					blr.add(i >> 8);
				}

				blr.add(blr_end);
				blr.add(blr_end);
				blr.add(blr_eoc);

				AutoRequest request;
				request.compile(tdbb, blr.begin(), blr.getCount());

				HalfStaticArray<USHORT, 5> hasRecord;

				EXE_start(tdbb, request, transaction);
				EXE_receive(tdbb, request, 1, fields.getCount() * sizeof(USHORT),
					(UCHAR*) hasRecord.getBuffer(fields.getCount()));

				Arg::Gds errs(isc_no_meta_update);
				bool hasError = false;

				for (FB_SIZE_T i = 0; i < fields.getCount(); ++i)
				{
					if (hasRecord[i])
					{
						hasError = true;
						errs << Arg::Gds(isc_cannot_make_not_null) <<
								(*relation->rel_fields)[fields[i]]->fld_name <<
								relation->rel_name;
					}
				}

				if (hasError)
					ERR_post(errs);
			}
		}

		break;
	}

	return false;
}


// Store RDB$CONTEXT_TYPE in RDB$VIEW_RELATIONS when restoring legacy backup.
static bool store_view_context_type(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
		{
			// If RDB$PACKAGE_NAME IS NOT NULL or no record is found in RDB$RELATIONS,
			// the context is a procedure;
			ViewContextType vct = VCT_PROCEDURE;

			AutoRequest handle1;
			FOR (REQUEST_HANDLE handle1 TRANSACTION_HANDLE transaction)
				VRL IN RDB$VIEW_RELATIONS CROSS
				REL IN RDB$RELATIONS OVER RDB$RELATION_NAME WITH
					VRL.RDB$VIEW_NAME = work->dfw_name.c_str() AND
					VRL.RDB$VIEW_CONTEXT = work->dfw_id AND
					VRL.RDB$PACKAGE_NAME MISSING
			{
				vct = (REL.RDB$VIEW_BLR.NULL ? VCT_TABLE : VCT_VIEW);
			}
			END_FOR

			AutoRequest handle2;
			FOR (REQUEST_HANDLE handle2 TRANSACTION_HANDLE transaction)
				VRL IN RDB$VIEW_RELATIONS WITH
					VRL.RDB$VIEW_NAME = work->dfw_name.c_str() AND
					VRL.RDB$VIEW_CONTEXT = work->dfw_id
			{
				MODIFY VRL USING
					VRL.RDB$CONTEXT_TYPE.NULL = FALSE;
					VRL.RDB$CONTEXT_TYPE = (SSHORT) vct;
				END_MODIFY
			}
			END_FOR
		}
		break;
	}

	return false;
}


static bool user_management(thread_db* /*tdbb*/, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	u s e r _ m a n a g e m e n t
 *
 **************************************
 *
 * Commit in security database
 *
 **************************************/

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		transaction->getUserManagement()->execute(work->dfw_id);
		return true;

	case 4:
		transaction->getUserManagement()->commit();	// safe to be called multiple times
		break;
	}

	return false;
}


// Drop dependencies of a package header.
static bool drop_package_header(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
		MET_delete_dependencies(tdbb, work->dfw_name, obj_package_body, transaction);
		MET_delete_dependencies(tdbb, work->dfw_name, obj_package_header, transaction);
		break;
	}

	return false;
}


// Drop dependencies of a package body.
static bool drop_package_body(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
		MET_delete_dependencies(tdbb, work->dfw_name, obj_package_body, transaction);
		break;
	}

	return false;
}


static bool grant_privileges(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	g r a n t _ p r i v i l e g e s
 *
 **************************************
 *
 * Functional description
 *	Compute access control list from SQL privileges.
 *
 **************************************/
	switch (phase)
	{
	case 1:
		return true;

	case 2:
		GRANT_privileges(tdbb, work->dfw_name, work->dfw_id, transaction);
		break;

	default:
		break;
	}

	return false;
}


static bool create_expression_index(thread_db* tdbb, SSHORT phase, DeferredWork* work,
	jrd_tra* transaction)
{
/**************************************
 *
 *	c r e a t e  _ e x p r e s s i o n _ i n d e x
 *
 **************************************
 *
 * Functional description
 *	Create a new expression index.
 *
 **************************************/
	switch (phase)
	{
	case 0:
		cleanup_index_creation(tdbb, work, transaction);
		MET_delete_dependencies(tdbb, work->dfw_name, obj_expression_index, transaction);
		return false;

	case 1:
	case 2:
		return true;

	case 3:
		{
			jrd_rel* relation = NULL;
			index_desc idx;
			MemoryPool* new_pool = NULL;

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

			MOVE_CLEAR(&idx, sizeof(index_desc));

			AutoCacheRequest request(tdbb, irq_c_exp_index, IRQ_REQUESTS);

			FOR(REQUEST_HANDLE request)
				IDX IN RDB$INDICES CROSS
				REL IN RDB$RELATIONS OVER RDB$RELATION_NAME WITH
				IDX.RDB$EXPRESSION_BLR NOT MISSING AND
				IDX.RDB$INDEX_NAME EQ work->dfw_name.c_str()
			{
				if (!relation)
				{
					relation = MET_relation(tdbb, REL.RDB$RELATION_ID);
					if (relation->rel_name.length() == 0) {
						relation->rel_name = REL.RDB$RELATION_NAME;
					}

					if (IDX.RDB$INDEX_ID && IDX.RDB$STATISTICS < 0.0)
					{
						SelectivityList selectivity(*tdbb->getDefaultPool());
						const USHORT localId = IDX.RDB$INDEX_ID - 1;
						IDX_statistics(tdbb, relation, localId, selectivity);
						DFW_update_index(work->dfw_name.c_str(), localId, selectivity, transaction);

						return false;
					}

					if (IDX.RDB$INDEX_ID)
					{
						IDX_delete_index(tdbb, relation, IDX.RDB$INDEX_ID - 1);
						MET_delete_dependencies(tdbb, work->dfw_name, obj_expression_index, transaction);
						MODIFY IDX
							IDX.RDB$INDEX_ID.NULL = TRUE;
						END_MODIFY
					}

					if (IDX.RDB$INDEX_INACTIVE)
						return false;

					if (IDX.RDB$SEGMENT_COUNT)
					{
						// Msg359: segments not allowed in expression index %s
						ERR_post(Arg::Gds(isc_no_meta_update) <<
							Arg::Gds(isc_no_segments_err) << Arg::Str(work->dfw_name));
					}
					if (IDX.RDB$UNIQUE_FLAG)
						idx.idx_flags |= idx_unique;
					if (IDX.RDB$INDEX_TYPE == 1)
						idx.idx_flags |= idx_descending;

					CompilerScratch* csb = NULL;
					// allocate a new pool to contain the expression tree for the expression index
					new_pool = attachment->createPool();
					{ // scope
						Jrd::ContextPoolHolder context(tdbb, new_pool);
						MET_scan_relation(tdbb, relation);

						if (!IDX.RDB$EXPRESSION_BLR.NULL)
						{
							idx.idx_expression = static_cast<ValueExprNode*>(MET_get_dependencies(
								tdbb, relation, NULL, 0, NULL, &IDX.RDB$EXPRESSION_BLR,
								&idx.idx_expression_statement, &csb, work->dfw_name, obj_expression_index, 0,
								transaction));
						}
					} // end scope

					// fake a description of the index

					idx.idx_count = 1;
					idx.idx_flags |= idx_expressn;
					idx.idx_expression->getDesc(tdbb, csb, &idx.idx_expression_desc);
					idx.idx_rpt[0].idx_itype =
						DFW_assign_index_type(tdbb, work->dfw_name,
						idx.idx_expression_desc.dsc_dtype,
						idx.idx_expression_desc.dsc_sub_type);
					idx.idx_rpt[0].idx_selectivity = 0;

					delete csb;
				}
			}
			END_FOR

			if (!relation)
			{
				if (new_pool)
					attachment->deletePool(new_pool);

				// Msg308: can't create index %s
				ERR_post(Arg::Gds(isc_no_meta_update) <<
					Arg::Gds(isc_idx_create_err) << Arg::Str(work->dfw_name));
			}

			// Actually create the index

			// Protect relation from modification to create consistent index
			ProtectRelations protectRelation(tdbb, transaction, relation);

			SelectivityList selectivity(*tdbb->getDefaultPool());

			jrd_tra* const current_transaction = tdbb->getTransaction();
			jrd_req* const current_request = tdbb->getRequest();

			try
			{
				fb_assert(work->dfw_id <= dbb->dbb_max_idx);
				idx.idx_id = work->dfw_id;
				IDX_create_index(tdbb, relation, &idx, work->dfw_name.c_str(), &work->dfw_id,
					transaction, selectivity);

				fb_assert(work->dfw_id == idx.idx_id);
			}
			catch (const Exception&)
			{
				tdbb->setTransaction(current_transaction);
				tdbb->setRequest(current_request);

				// Get rid of the expression statement.
				idx.idx_expression_statement->release(tdbb);

				throw;
			}

			tdbb->setTransaction(current_transaction);
			tdbb->setRequest(current_request);

			DFW_update_index(work->dfw_name.c_str(), idx.idx_id, selectivity, transaction);

			// Get rid of the expression statement.
			idx.idx_expression_statement->release(tdbb);
		}
		break;

	default:
		break;
	}

	return false;
}


static void check_computed_dependencies(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& fieldName)
{
/**************************************
 *
 *	c h e c k _ c o m p u t e d _ d e p e n d e n c i e s
 *
 **************************************
 *
 * Functional description
 *	Checks if a computed field has circular dependencies.
 *
 **************************************/
	SET_TDBB(tdbb);

	bool err = false;
	Firebird::SortedObjectsArray<MetaName> sortedNames(*tdbb->getDefaultPool());
	Firebird::ObjectsArray<MetaName> names;

	sortedNames.add(fieldName);
	names.add(fieldName);

	for (FB_SIZE_T pos = 0; !err && pos < names.getCount(); ++pos)
	{
		AutoCacheRequest request(tdbb, irq_comp_circ_dpd, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			DEP IN RDB$DEPENDENCIES CROSS
			RFL IN RDB$RELATION_FIELDS WITH
				DEP.RDB$DEPENDENT_NAME EQ names[pos].c_str() AND
				DEP.RDB$DEPENDENT_TYPE EQ obj_computed AND
				DEP.RDB$DEPENDED_ON_TYPE = obj_relation AND
				RFL.RDB$RELATION_NAME = DEP.RDB$DEPENDED_ON_NAME AND
				RFL.RDB$FIELD_NAME = DEP.RDB$FIELD_NAME
		{
			MetaName fieldSource(RFL.RDB$FIELD_SOURCE);

			if (fieldName == fieldSource)
			{
				err = true;
				break;
			}

			if (!sortedNames.exist(fieldSource))
			{
				sortedNames.add(fieldSource);
				names.add(fieldSource);
			}
		}
		END_FOR
	}

	if (err)
	{
		ERR_post(Arg::Gds(isc_no_meta_update) <<
				 Arg::Gds(isc_circular_computed));
	}
}


static void check_dependencies(thread_db* tdbb,
							   const TEXT* dpdo_name,
							   const TEXT* field_name,
							   const TEXT* package_name,
							   int dpdo_type,
							   jrd_tra* transaction)
{
/**************************************
 *
 *	c h e c k _ d e p e n d e n c i e s
 *
 **************************************
 *
 * Functional description
 *	Check the dependency list for relation or relation.field
 *	before deleting such.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	const MetaName packageName(package_name);

	SLONG dep_counts[obj_type_MAX];
	for (int i = 0; i < obj_type_MAX; i++)
		dep_counts[i] = 0;

	if (field_name)
	{
		AutoCacheRequest request(tdbb, irq_ch_f_dpd, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request)
			DEP IN RDB$DEPENDENCIES
				WITH DEP.RDB$DEPENDED_ON_NAME EQ dpdo_name
				AND DEP.RDB$DEPENDED_ON_TYPE = dpdo_type
				AND DEP.RDB$FIELD_NAME EQ field_name
				AND DEP.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
				REDUCED TO DEP.RDB$DEPENDENT_NAME
		{
			// If the found object is also being deleted, there's no dependency

			if (!find_depend_in_dfw(tdbb, DEP.RDB$DEPENDENT_NAME, DEP.RDB$DEPENDENT_TYPE,
									0, transaction))
			{
				++dep_counts[DEP.RDB$DEPENDENT_TYPE];
			}
		}
		END_FOR
	}
	else
	{
		AutoCacheRequest request(tdbb, irq_ch_dpd, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request)
			DEP IN RDB$DEPENDENCIES
				WITH DEP.RDB$DEPENDED_ON_NAME EQ dpdo_name
				AND DEP.RDB$DEPENDED_ON_TYPE = dpdo_type
				AND DEP.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
				REDUCED TO DEP.RDB$DEPENDENT_NAME
		{
			// If the found object is also being deleted, there's no dependency

			if (!find_depend_in_dfw(tdbb, DEP.RDB$DEPENDENT_NAME, DEP.RDB$DEPENDENT_TYPE,
									0, transaction))
			{
				++dep_counts[DEP.RDB$DEPENDENT_TYPE];
			}
		}
		END_FOR
	}

	SLONG total = 0;
	for (int i = 0; i < obj_type_MAX; i++)
		total += dep_counts[i];

	if (!total)
		return;

	if (field_name)
	{
		string fld_name(dpdo_name);
		fld_name.append(".");
		fld_name.append(field_name);

		ERR_post(Arg::Gds(isc_no_meta_update) <<
				 Arg::Gds(isc_no_delete) <<						// Msg353: can not delete
				 Arg::Gds(isc_field_name) << Arg::Str(fld_name) <<
				 Arg::Gds(isc_dependency) << Arg::Num(total));	// Msg310: there are %ld dependencies
	}
	else
	{
		ERR_post(Arg::Gds(isc_no_meta_update) <<
				 Arg::Gds(isc_no_delete) <<						// can not delete
				 Arg::Gds(getErrorCodeByObjectType(dpdo_type)) <<
				 	Arg::Str(QualifiedName(dpdo_name, packageName).toString()) <<
				 Arg::Gds(isc_dependency) << Arg::Num(total));	// there are %ld dependencies
	}
}


static void check_filename(thread_db* tdbb, const Firebird::string& name, bool shareExpand)
{
/**************************************
 *
 *	c h e c k _ f i l e n a m e
 *
 **************************************
 *
 * Functional description
 *	Make sure that a file path doesn't contain an
 *	inet node name.
 *
 **************************************/
	if (tdbb->tdbb_flags & TDBB_replicator)
		return;

	const Firebird::PathName file_name(name.ToPathName());
	const bool valid = file_name.find("::") == Firebird::PathName::npos;

	if (!valid || ISC_check_if_remote(file_name, shareExpand)) {
		ERR_post(Arg::Gds(isc_no_meta_update) <<
				 Arg::Gds(isc_node_name_err));
		// Msg305: A node name is not permitted in a secondary, shadow, or log file name
	}

	if (!JRD_verify_database_access(file_name)) {
		ERR_post(Arg::Gds(isc_conf_access_denied) << Arg::Str("additional database file") <<
													 Arg::Str(name));
	}
}


static void cleanup_index_creation(thread_db* tdbb, DeferredWork* work, jrd_tra* transaction)
{
	Database* const dbb = tdbb->getDatabase();

	AutoRequest request;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction) IDXN IN RDB$INDICES CROSS
		IREL IN RDB$RELATIONS OVER RDB$RELATION_NAME
			WITH IDXN.RDB$INDEX_NAME EQ work->dfw_name.c_str()
			// dimitr: I have no idea why the condition below is required here
			AND IREL.RDB$VIEW_BLR MISSING // views do not have indices
	{
		jrd_rel* const relation = MET_lookup_relation(tdbb, IDXN.RDB$RELATION_NAME);
		RelationPages* const relPages = relation->getPages(tdbb, MAX_TRA_NUMBER, false);

		if (relPages && relPages->rel_index_root)
		{
			// We need to special handle temp tables with ON PRESERVE ROWS only
			const bool isTempIndex = (relation->rel_flags & REL_temp_conn) &&
				(relPages->rel_instance_id != 0);

			// Fetch the root index page and mark MUST_WRITE, and then
			// delete the index. It will also clean the index slot.

			if (work->dfw_id != dbb->dbb_max_idx)
			{
				WIN window(relPages->rel_pg_space_id, relPages->rel_index_root);
				CCH_FETCH(tdbb, &window, LCK_write, pag_root);
				CCH_MARK_MUST_WRITE(tdbb, &window);
				const bool tree_exists = BTR_delete_index(tdbb, &window, work->dfw_id);

				if (!isTempIndex) {
					work->dfw_id = dbb->dbb_max_idx;
				}
				else if (tree_exists)
				{
					IndexLock* const idx_lock = CMP_get_index_lock(tdbb, relation, work->dfw_id);

					if (idx_lock)
					{
						if (!--idx_lock->idl_count)
							LCK_release(tdbb, idx_lock->idl_lock);
					}
				}
			}

			if (!IDXN.RDB$INDEX_ID.NULL)
			{
				MODIFY IDXN USING
					IDXN.RDB$INDEX_ID.NULL = TRUE;
				END_MODIFY
			}

			if (!IDXN.RDB$FOREIGN_KEY.NULL)
			{
				index_desc idx;
				idx.idx_id = idx_invalid;
				idx.idx_flags = idx_foreign;

				jrd_rel* partner_relation = NULL;
				if (MET_lookup_partner(tdbb, relation, &idx, work->dfw_name.c_str()))
				{
					partner_relation = MET_lookup_relation_id(tdbb, idx.idx_primary_relation, true);
				}

				if (partner_relation)
				{
					// signal to other processes about new constraint
					relation->rel_flags |= REL_check_partners;
					LCK_lock(tdbb, relation->rel_partners_lock, LCK_EX, LCK_WAIT);
					LCK_release(tdbb, relation->rel_partners_lock);

					if (relation != partner_relation)
					{
						partner_relation->rel_flags |= REL_check_partners;
						LCK_lock(tdbb, partner_relation->rel_partners_lock, LCK_EX, LCK_WAIT);
						LCK_release(tdbb, partner_relation->rel_partners_lock);
					}
				}
			}
		}
	}
	END_FOR
}


static bool formatsAreEqual(const Format* old_format, const Format* new_format)
{
/**************************************
 *
 * Functional description
 *	Compare two format blocks
 *
 **************************************/

	if ((old_format->fmt_length != new_format->fmt_length) ||
		(old_format->fmt_count != new_format->fmt_count))
	{
		return false;
	}

	Format::fmt_desc_const_iterator old_desc = old_format->fmt_desc.begin();
	const Format::fmt_desc_const_iterator old_end = old_format->fmt_desc.end();

	Format::fmt_desc_const_iterator new_desc = new_format->fmt_desc.begin();

	while (old_desc != old_end)
	{
		if ((old_desc->dsc_dtype != new_desc->dsc_dtype) ||
			(old_desc->dsc_scale != new_desc->dsc_scale) ||
			(old_desc->dsc_length != new_desc->dsc_length) ||
			(old_desc->dsc_sub_type != new_desc->dsc_sub_type) ||
			(old_desc->dsc_flags != new_desc->dsc_flags) ||
			(old_desc->dsc_address != new_desc->dsc_address))
		{
			return false;
		}

		++new_desc;
		++old_desc;
	}

	return true;
}


static bool compute_security(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	c o m p u t e _ s e c u r i t y
 *
 **************************************
 *
 * Functional description
 *	There was a change in a security class.  Recompute everything
 *	it touches.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			// Get security class.  This may return NULL if it doesn't exist

			SCL_clear_classes(tdbb, work->dfw_name.c_str());

			AutoRequest handle;
			FOR(REQUEST_HANDLE handle) X IN RDB$DATABASE
				WITH X.RDB$SECURITY_CLASS EQ work->dfw_name.c_str()
			{
				attachment->att_security_class = SCL_get_class(tdbb, work->dfw_name.c_str());
			}
			END_FOR
		}
		break;
	}

	return false;
}


static bool modify_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	m o d i f y _ i n d e x
 *
 **************************************
 *
 * Functional description
 *	Create\drop an index or change the state of an index between active/inactive.
 *	If index owns by global temporary table with on commit preserve rows scope
 *	change index instance for this temporary table too. For "create index" work
 *	item create base index instance before temp index instance. For index
 *	deletion delete temp index instance first to release index usage counter
 *	before deletion of base index instance.
 **************************************/

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

	bool is_create = true;
	dfw_task_routine task_routine = NULL;

	switch (work->dfw_type)
	{
		case dfw_create_index :
			task_routine = create_index;
			break;

		case dfw_create_expression_index :
			task_routine = create_expression_index;
			break;

		case dfw_delete_index :
		case dfw_delete_expression_index :
			task_routine = delete_index;
			is_create = false;
			break;
	}
	fb_assert(task_routine);

	bool more = false, more2 = false;

	if (is_create) {
		more = (*task_routine)(tdbb, phase, work, transaction);
	}

	bool gtt_preserve = false;
	jrd_rel* relation = NULL;

	if (is_create)
	{
		PreparedStatement::Builder sql;
		SLONG rdbRelationID;
		SLONG rdbRelationType;
		sql << "select"
			<< sql("rel.rdb$relation_id,", rdbRelationID)
			<< sql("rel.rdb$relation_type", rdbRelationType)
			<< "from rdb$indices idx join rdb$relations rel using (rdb$relation_name)"
			<< "where idx.rdb$index_name = " << work->dfw_name
			<< "  and rel.rdb$relation_id is not null";
		AutoPreparedStatement ps(attachment->prepareStatement(tdbb,
			attachment->getSysTransaction(), sql));
		AutoResultSet rs(ps->executeQuery(tdbb, attachment->getSysTransaction()));

		while (rs->fetch(tdbb))
		{
			gtt_preserve = (rdbRelationType == rel_global_temp_preserve);
			relation = MET_lookup_relation_id(tdbb, rdbRelationID, false);
		}
	}
	else if (work->dfw_id > 0)
	{
		relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
		gtt_preserve = (relation) && (relation->rel_flags & REL_temp_conn);
	}

	if (gtt_preserve && relation)
	{
		tdbb->tdbb_flags &= ~TDBB_use_db_page_space;
		try {
			if (relation->getPages(tdbb, MAX_TRA_NUMBER, false)) {
				more2 = (*task_routine) (tdbb, phase, work, transaction);
			}
			tdbb->tdbb_flags |= TDBB_use_db_page_space;
		}
		catch (...)
		{
			tdbb->tdbb_flags |= TDBB_use_db_page_space;
			throw;
		}
	}

	if (!is_create) {
		more = (*task_routine)(tdbb, phase, work, transaction);
	}

	return (more || more2);
}


static bool create_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	c r e a t e _ i n d e x
 *
 **************************************
 *
 * Functional description
 *	Create a new index or change the state of an index between active/inactive.
 *
 **************************************/
	AutoCacheRequest request;
	jrd_rel* relation;
	jrd_rel* partner_relation;
	index_desc idx;
	int key_count;

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

	switch (phase)
	{
	case 0:
		cleanup_index_creation(tdbb, work, transaction);
		return false;

	case 1:
	case 2:
		return true;

	case 3:
		key_count = 0;
		relation = NULL;
		idx.idx_flags = 0;

		// Fetch the information necessary to create the index.  On the first
		// time thru, check to see if the index already exists.  If so, delete
		// it.  If the index inactive flag is set, don't create the index

		request.reset(tdbb, irq_c_index, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			IDX IN RDB$INDICES CROSS
			REL IN RDB$RELATIONS OVER RDB$RELATION_NAME
				WITH IDX.RDB$INDEX_NAME EQ work->dfw_name.c_str()
		{
			relation = MET_lookup_relation_id(tdbb, REL.RDB$RELATION_ID, false);
			if (!relation)
			{
				ERR_post(Arg::Gds(isc_no_meta_update) <<
						 Arg::Gds(isc_idx_create_err) << Arg::Str(work->dfw_name));
				// Msg308: can't create index %s
			}

			if (IDX.RDB$INDEX_ID && IDX.RDB$STATISTICS < 0.0)
			{
				// we need to know if this relation is temporary or not
				MET_scan_relation(tdbb, relation);

				// no need to recalculate statistics for base instance of GTT
				RelationPages* relPages = relation->getPages(tdbb, MAX_TRA_NUMBER, false);
				const bool isTempInstance = relation->isTemporary() &&
					relPages && (relPages->rel_instance_id != 0);

				if (isTempInstance || !relation->isTemporary())
				{
					SelectivityList selectivity(*tdbb->getDefaultPool());
					const USHORT id = IDX.RDB$INDEX_ID - 1;
					IDX_statistics(tdbb, relation, id, selectivity);
					DFW_update_index(work->dfw_name.c_str(), id, selectivity, transaction);
				}

				return false;
			}

			if (IDX.RDB$INDEX_ID)
			{
				IDX_delete_index(tdbb, relation, (USHORT)(IDX.RDB$INDEX_ID - 1));

				AutoCacheRequest request2(tdbb, irq_c_index_m, IRQ_REQUESTS);

				FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
					IDXM IN RDB$INDICES WITH IDXM.RDB$INDEX_NAME EQ work->dfw_name.c_str()
				{
					MODIFY IDXM
						IDXM.RDB$INDEX_ID.NULL = TRUE;
					END_MODIFY
				}
				END_FOR
			}

			if (IDX.RDB$INDEX_INACTIVE)
				return false;

			idx.idx_count = IDX.RDB$SEGMENT_COUNT;

			if (!idx.idx_count || idx.idx_count > MAX_INDEX_SEGMENTS)
			{
				if (!idx.idx_count)
				{
					ERR_post(Arg::Gds(isc_no_meta_update) <<
							 Arg::Gds(isc_idx_seg_err) << Arg::Str(work->dfw_name));
					// Msg304: segment count of 0 defined for index %s
				}
				else
				{
					ERR_post(Arg::Gds(isc_no_meta_update) <<
							 Arg::Gds(isc_idx_key_err) << Arg::Str(work->dfw_name));
					// Msg311: too many keys defined for index %s
				}
			}

			if (IDX.RDB$UNIQUE_FLAG)
				idx.idx_flags |= idx_unique;
			if (IDX.RDB$INDEX_TYPE == 1)
				idx.idx_flags |= idx_descending;
			if (!IDX.RDB$FOREIGN_KEY.NULL)
				idx.idx_flags |= idx_foreign;

			AutoCacheRequest rc_request(tdbb, irq_c_index_rc, IRQ_REQUESTS);

			FOR(REQUEST_HANDLE rc_request TRANSACTION_HANDLE transaction)
				RC IN RDB$RELATION_CONSTRAINTS WITH
				RC.RDB$INDEX_NAME EQ work->dfw_name.c_str() AND
				RC.RDB$CONSTRAINT_TYPE = PRIMARY_KEY
			{
				idx.idx_flags |= idx_primary;
			}
			END_FOR

			// Here we need dirty reads from database (first of all from
			// RDB$RELATION_FIELDS and RDB$FIELDS - tables not directly related
			// with index to be created and it's dfw_name). Missing it breaks gbak,
			// and appears can break other applications.

			AutoCacheRequest seg_request(tdbb, irq_c_index_seg, IRQ_REQUESTS);

			FOR(REQUEST_HANDLE seg_request)
				SEG IN RDB$INDEX_SEGMENTS CROSS
				RFR IN RDB$RELATION_FIELDS CROSS
				FLD IN RDB$FIELDS
					WITH SEG.RDB$INDEX_NAME EQ work->dfw_name.c_str()
					AND RFR.RDB$RELATION_NAME EQ relation->rel_name.c_str()
					AND RFR.RDB$FIELD_NAME EQ SEG.RDB$FIELD_NAME
					AND FLD.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE
			{
				if (++key_count > idx.idx_count || SEG.RDB$FIELD_POSITION > idx.idx_count ||
					FLD.RDB$FIELD_TYPE == blr_blob || !FLD.RDB$DIMENSIONS.NULL)
				{
					if (key_count > idx.idx_count)
					{
						ERR_post(Arg::Gds(isc_no_meta_update) <<
								 Arg::Gds(isc_idx_key_err) << Arg::Str(work->dfw_name));
						// Msg311: too many keys defined for index %s
					}
					else if (SEG.RDB$FIELD_POSITION > idx.idx_count)
					{
						fb_utils::exact_name(RFR.RDB$FIELD_NAME);
						ERR_post(Arg::Gds(isc_no_meta_update) <<
								 Arg::Gds(isc_inval_key_posn) <<
								 // Msg358: invalid key position
								 Arg::Gds(isc_field_name) << Arg::Str(RFR.RDB$FIELD_NAME) <<
								 Arg::Gds(isc_index_name) << Arg::Str(work->dfw_name));
					}
					else if (FLD.RDB$FIELD_TYPE == blr_blob)
					{
						ERR_post(Arg::Gds(isc_no_meta_update) <<
								 Arg::Gds(isc_blob_idx_err) << Arg::Str(work->dfw_name));
						// Msg350: attempt to index blob column in index %s
					}
					else
					{
						ERR_post(Arg::Gds(isc_no_meta_update) <<
								 Arg::Gds(isc_array_idx_err) << Arg::Str(work->dfw_name));
						// Msg351: attempt to index array column in index %s
					}
				}

				idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_field = RFR.RDB$FIELD_ID;

				if (FLD.RDB$CHARACTER_SET_ID.NULL)
					FLD.RDB$CHARACTER_SET_ID = CS_NONE;

				SSHORT collate;
				if (!RFR.RDB$COLLATION_ID.NULL)
					collate = RFR.RDB$COLLATION_ID;
				else if (!FLD.RDB$COLLATION_ID.NULL)
					collate = FLD.RDB$COLLATION_ID;
				else
					collate = COLLATE_NONE;

				const SSHORT text_type = INTL_CS_COLL_TO_TTYPE(FLD.RDB$CHARACTER_SET_ID, collate);
				idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_itype =
					DFW_assign_index_type(tdbb, work->dfw_name, gds_cvt_blr_dtype[FLD.RDB$FIELD_TYPE], text_type);

				// Initialize selectivity to zero. Otherwise random rubbish makes its way into database
				idx.idx_rpt[SEG.RDB$FIELD_POSITION].idx_selectivity = 0;
			}
			END_FOR
		}
		END_FOR

		if (!relation)
		{
			// The record was not found in RDB$INDICES.
			// Apparently the index was dropped in the same transaction.
			return false;
		}

		if (key_count != idx.idx_count)
		{
			ERR_post(Arg::Gds(isc_no_meta_update) <<
					 Arg::Gds(isc_key_field_err) << Arg::Str(work->dfw_name));
			// Msg352: too few key columns found for index %s (incorrect column name?)
		}

		// Make sure the relation info is all current

		MET_scan_relation(tdbb, relation);

		if (relation->rel_view_rse)
		{
			ERR_post(Arg::Gds(isc_no_meta_update) <<
					 Arg::Gds(isc_idx_create_err) << Arg::Str(work->dfw_name));
			// Msg308: can't create index %s
		}

		// Actually create the index

		partner_relation = NULL;

		// Protect relation from modification to create consistent index
		ProtectRelations protectRelations(tdbb, transaction);
		protectRelations.addRelation(relation);

		if (idx.idx_flags & idx_foreign)
		{
			idx.idx_id = idx_invalid;

			if (MET_lookup_partner(tdbb, relation, &idx, work->dfw_name.c_str()))
			{
				partner_relation = MET_lookup_relation_id(tdbb, idx.idx_primary_relation, true);
			}

			if (!partner_relation)
			{
				MetaName constraint_name;
				MET_lookup_cnstrt_for_index(tdbb, constraint_name, work->dfw_name);
				ERR_post(Arg::Gds(isc_partner_idx_not_found) << Arg::Str(constraint_name));
			}

			// Get an protected_read lock on the both relations if the index being
			// defined enforces a foreign key constraint. This will prevent
			// the constraint from being violated during index construction.

			protectRelations.addRelation(partner_relation);

			int bad_segment;
			if (!IDX_check_master_types(tdbb, idx, partner_relation, bad_segment))
			{
				ERR_post(Arg::Gds(isc_no_meta_update) <<
							Arg::Gds(isc_partner_idx_incompat_type) << Arg::Num(bad_segment + 1));
			}

			/*** hvlad: this code was never called but i preserve it for Claudio review and decision

			// CVC: Currently, the server doesn't enforce FK creation more than at DYN level.
			// If DYN is bypassed, then FK creation succeeds and operation will fail at run-time.
			// The aim is to check REFERENCES at DDL time instead of DML time and behave accordingly
			// to ANSI SQL rules for REFERENCES rights.
			// For testing purposes, I'm calling SCL_check_index, although most of the DFW ops are
			// carried using internal metadata structures that are refreshed from system tables.

			// Don't bother if the master's owner is the same than the detail's owner.
			// If both tables aren't defined in the same session, partner_relation->rel_owner_name
			// won't be loaded hence, we need to be careful about null pointers.

			if (relation->rel_owner_name.length() == 0 ||
				partner_relation->rel_owner_name.length() == 0 ||
				relation->rel_owner_name != partner_relation->rel_owner_name)
			{
				SCL_check_index(tdbb, partner_relation->rel_name,
								idx.idx_id + 1, SCL_references);
			}
			***/
		}

		protectRelations.lock();

		fb_assert(work->dfw_id <= dbb->dbb_max_idx);
		idx.idx_id = work->dfw_id;
		SelectivityList selectivity(*tdbb->getDefaultPool());
		IDX_create_index(tdbb, relation, &idx, work->dfw_name.c_str(),
						&work->dfw_id, transaction, selectivity);
		fb_assert(work->dfw_id == idx.idx_id);
		DFW_update_index(work->dfw_name.c_str(), idx.idx_id, selectivity, transaction);

		if (partner_relation)
		{
			// signal to other processes about new constraint
			relation->rel_flags |= REL_check_partners;
			LCK_lock(tdbb, relation->rel_partners_lock, LCK_EX, LCK_WAIT);
			LCK_release(tdbb, relation->rel_partners_lock);

			if (relation != partner_relation)
			{
				partner_relation->rel_flags |= REL_check_partners;
				LCK_lock(tdbb, partner_relation->rel_partners_lock, LCK_EX, LCK_WAIT);
				LCK_release(tdbb, partner_relation->rel_partners_lock);
			}
		}

		break;
	}

	return false;
}


static bool create_relation(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	c r e a t e _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Create a new relation.
 *
 **************************************/
	AutoCacheRequest request;
	jrd_rel* relation;
	USHORT rel_id, external_flag;
	bid blob_id;
	AutoRequest handle;
	Lock* lock;

	blob_id.clear();

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

	const USHORT local_min_relation_id = USER_DEF_REL_INIT_ID;

	switch (phase)
	{
	case 0:
		// We need to cleanup RDB$PAGES and pages if they were added at phase 3.
		request.reset(tdbb, irq_c_relation3, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request)
			X IN RDB$RELATIONS WITH
				X.RDB$RELATION_NAME EQ work->dfw_name.c_str() AND
				X.RDB$RELATION_ID NOT MISSING
		{
			rel_id = X.RDB$RELATION_ID;

			if ( (relation = MET_lookup_relation_id(tdbb, rel_id, false)) )
			{
				RelationPages* const relPages = relation->getBasePages();

				if (relPages->rel_index_root)
					IDX_delete_indices(tdbb, relation, relPages);

				if (relPages->rel_pages)
					DPM_delete_relation(tdbb, relation);

				// Mark relation in the cache as dropped
				relation->rel_flags |= REL_deleted;
			}
		}
		END_FOR

		if (work->dfw_lock)
		{
			LCK_release(tdbb, work->dfw_lock);
			delete work->dfw_lock;
			work->dfw_lock = NULL;
		}
		break;

	case 1:
	case 2:
		return true;

	case 3:
		// Take a relation lock on rel id -1 before actually generating a relation id.

		work->dfw_lock = lock = FB_NEW_RPT(*tdbb->getDefaultPool(), 0)
			Lock(tdbb, sizeof(SLONG), LCK_relation);
		lock->setKey(-1);

		LCK_lock(tdbb, lock, LCK_EX, LCK_WAIT);

		/* Assign a relation ID and dbkey length to the new relation.
		   Probe the candidate relation ID returned from the system
		   relation RDB$DATABASE to make sure it isn't already assigned.
		   This can happen from nefarious manipulation of RDB$DATABASE
		   or wraparound of the next relation ID. Keep looking for a
		   usable relation ID until the search space is exhausted. */

		rel_id = 0;
		request.reset(tdbb, irq_c_relation, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			X IN RDB$DATABASE CROSS Y IN RDB$RELATIONS WITH
				Y.RDB$RELATION_NAME EQ work->dfw_name.c_str()
		{
			blob_id = Y.RDB$VIEW_BLR;
			external_flag = Y.RDB$EXTERNAL_FILE[0];

			MODIFY X USING
				rel_id = X.RDB$RELATION_ID;

				if (rel_id < local_min_relation_id || rel_id > MAX_RELATION_ID)
					rel_id = X.RDB$RELATION_ID = local_min_relation_id;

				// Roman Simakov: We need to return deleted relations to skip them.
				// This maybe result of cleanup failure after phase 3.
				while ( (relation = MET_lookup_relation_id(tdbb, rel_id++, true)) )
				{
					if (rel_id < local_min_relation_id || rel_id > MAX_RELATION_ID)
						rel_id = local_min_relation_id;

					if (rel_id == X.RDB$RELATION_ID)
					{
						ERR_post(Arg::Gds(isc_no_meta_update) <<
								 Arg::Gds(isc_table_name) << Arg::Str(work->dfw_name) <<
								 Arg::Gds(isc_imp_exc));
					}
				}

				X.RDB$RELATION_ID = (rel_id > MAX_RELATION_ID) ? local_min_relation_id : rel_id;

				MODIFY Y USING
					Y.RDB$RELATION_ID = --rel_id;
					if (blob_id.isEmpty())
						Y.RDB$DBKEY_LENGTH = 8;
					else
					{
						// update the dbkey length to include each of the base relations
						Y.RDB$DBKEY_LENGTH = 0;

						handle.reset();

						FOR(REQUEST_HANDLE handle)
							Z IN RDB$VIEW_RELATIONS CROSS
							R IN RDB$RELATIONS OVER RDB$RELATION_NAME
							WITH Z.RDB$VIEW_NAME = work->dfw_name.c_str() AND
								 (Z.RDB$CONTEXT_TYPE = VCT_TABLE OR
								  Z.RDB$CONTEXT_TYPE = VCT_VIEW)
						{
							Y.RDB$DBKEY_LENGTH += R.RDB$DBKEY_LENGTH;
						}
						END_FOR
					}
				END_MODIFY
			END_MODIFY
		}
		END_FOR

		LCK_release(tdbb, lock);
		delete lock;
		work->dfw_lock = NULL;

		// if this is not a view, create the relation

		if (rel_id && blob_id.isEmpty() && !external_flag)
		{
			relation = MET_relation(tdbb, rel_id);
			DPM_create_relation(tdbb, relation);
		}

		return true;

	case 4:

		// get the relation and flag it to check for dependencies
		// in the view blr (if it exists) and any computed fields

		request.reset(tdbb, irq_c_relation2, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE request)
			X IN RDB$RELATIONS WITH
				X.RDB$RELATION_NAME EQ work->dfw_name.c_str()
		{
			rel_id = X.RDB$RELATION_ID;
			relation = MET_relation(tdbb, rel_id);
			relation->rel_flags |= REL_get_dependencies;

			relation->rel_flags &= ~REL_scanned;
			DFW_post_work(transaction, dfw_scan_relation, NULL, rel_id);
		}
		END_FOR

		break;
	}

	return false;
}


static bool create_trigger(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	c r e a t e _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *	Perform required actions on creation of trigger.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			const bool compile = !work->findArg(dfw_arg_check_blr);
			get_trigger_dependencies(work, compile, transaction);
			return true;
		}

	case 4:
		{
			if (!work->findArg(dfw_arg_rel_name))
			{
				const DeferredWork* const arg = work->findArg(dfw_arg_trg_type);
				fb_assert(arg);

				if (arg)
				{
					// ASF: arg->dfw_id is RDB$TRIGGER_TYPE truncated to USHORT
					if ((arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB)
					{
						unsigned triggerKind = arg->dfw_id & ~TRIGGER_TYPE_DB;
						MET_release_triggers(tdbb, &tdbb->getAttachment()->att_triggers[triggerKind], true);
						MET_load_db_triggers(tdbb, triggerKind);
					}
					else if ((arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DDL)
					{
						MET_release_triggers(tdbb, &tdbb->getAttachment()->att_ddl_triggers, true);
						MET_load_ddl_triggers(tdbb);
					}
				}
			}
		}
		break;
	}

	return false;
}


#define DEBUG_REBUILD_INTL(A)

static string remove_icu_info_from_attributes(const string& charsetName, const string& specificAttributes)
{
	Firebird::AutoPtr<charset> cs(FB_NEW charset);
	memset(cs, 0, sizeof(*cs));

	if (IntlManager::lookupCharSet(charsetName, cs))
	{
		AutoPtr<Jrd::CharSet> charSet(Jrd::CharSet::createInstance(*getDefaultMemoryPool(), 0, cs));
		IntlUtil::SpecificAttributesMap map;
		if (IntlUtil::parseSpecificAttributes(charSet, specificAttributes.length(),
			(const UCHAR*) specificAttributes.begin(), &map))
		{
			map.remove("ICU-VERSION");
			map.remove("COLL-VERSION");
			return IntlUtil::generateSpecificAttributes(charSet, map);
		}
	}

	return specificAttributes;
}


static void setupSpecificCollationAttributes(thread_db* tdbb, jrd_tra* transaction,
	const USHORT charSetId, const char* collationName, bool dropIcuInfo)
{
/**************************************
 *
 *	setupSpecificCollationAttributes
 *
 **************************************
 *
 * Functional description
 *
 **************************************/

	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();
	AutoCacheRequest handle(tdbb, drq_m_coll_attrs, DYN_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
		COLL IN RDB$COLLATIONS
		CROSS CS IN RDB$CHARACTER_SETS
			OVER RDB$CHARACTER_SET_ID
		WITH COLL.RDB$COLLATION_NAME EQ collationName AND
			 COLL.RDB$CHARACTER_SET_ID EQ charSetId
	{
		SLONG length = 0;
		HalfStaticArray<UCHAR, BUFFER_SMALL> buffer;

		if (!COLL.RDB$SPECIFIC_ATTRIBUTES.NULL)
		{
			blb* blob = blb::open(tdbb, transaction, &COLL.RDB$SPECIFIC_ATTRIBUTES);
			length = blob->blb_length + 10;
			length = blob->BLB_get_data(tdbb, buffer.getBuffer(length), length);
		}

		const string specificAttributes((const char*) buffer.begin(), length);
		MetaName charsetName(CS.RDB$CHARACTER_SET_NAME);
		string icuLessAttributes = dropIcuInfo ?
			remove_icu_info_from_attributes(charsetName.c_str(), specificAttributes) : specificAttributes;
		string newSpecificAttributes;

		// ASF: If setupCollationAttributes fail we store the original
		// attributes. This should be what we want for new databases
		// and restores. CREATE COLLATION will fail in DYN.
		if (IntlManager::setupCollationAttributes(
				fb_utils::exact_name(COLL.RDB$BASE_COLLATION_NAME.NULL ?
					COLL.RDB$COLLATION_NAME : COLL.RDB$BASE_COLLATION_NAME),
				fb_utils::exact_name(CS.RDB$CHARACTER_SET_NAME),
				icuLessAttributes, newSpecificAttributes) &&
			newSpecificAttributes != specificAttributes) // if nothing changed, we do nothing
		{
			MODIFY COLL USING
				if (newSpecificAttributes.isEmpty())
					COLL.RDB$SPECIFIC_ATTRIBUTES.NULL = TRUE;
				else
				{
					COLL.RDB$SPECIFIC_ATTRIBUTES.NULL = FALSE;
					attachment->storeMetaDataBlob(tdbb, transaction,
						&COLL.RDB$SPECIFIC_ATTRIBUTES, newSpecificAttributes);
				}
			END_MODIFY
		}
	}
	END_FOR
}


static bool create_collation(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	c r e a t e _ c o l l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Get collation id or setup specific attributes.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
		setupSpecificCollationAttributes(tdbb, transaction, TTYPE_TO_CHARSET(work->dfw_id),
			work->dfw_name.c_str(), false);

		if (!(transaction->tra_flags & TRA_system) &&	// avoid run during database creation
			!INTL_defined_type(tdbb, work->dfw_id))
		{
			setupSpecificCollationAttributes(tdbb, transaction, TTYPE_TO_CHARSET(work->dfw_id),
				work->dfw_name.c_str(), true);
		}
		break;
	}

	return false;
}


void DFW_reset_icu(thread_db* tdbb)
{
/**************************************
 *
 *	r e s e t I c u
 *
 **************************************
 *
 * Functional description
 *	Fix database for use with other ICU version.
 *	Formally has nothing to do with DFW,
 *	but adding new .epp module is worse.
 *	Next, it's using some DFW code.
 *
 **************************************/
	SET_TDBB(tdbb);

	jrd_tra* transaction = NULL;
	jrd_tra* oldTransaction = tdbb->getTransaction();

	try
	{
		Attachment* attachment = tdbb->getAttachment();
		transaction = TRA_start(tdbb, 0, 0);
		tdbb->setTransaction(transaction);

		SortedArray<MetaName> indices;
		ProtectRelations tables(tdbb, transaction);

		// Get list of affected indices & tables
		const char* indSql =
			"select ind.RDB$INDEX_NAME, rel.RDB$RELATION_ID,"
			" coalesce(coll.RDB$BASE_COLLATION_NAME, coll.RDB$COLLATION_NAME), cs.RDB$CHARACTER_SET_NAME, "
			" coll.RDB$SPECIFIC_ATTRIBUTES "
			"from RDB$INDICES ind "
			"join RDB$RELATIONS rel on ind.RDB$RELATION_NAME = rel.RDB$RELATION_NAME "
			"join RDB$INDEX_SEGMENTS seg on ind.RDB$INDEX_NAME = seg.RDB$INDEX_NAME "
			"join RDB$RELATION_FIELDS rfl on rfl.RDB$RELATION_NAME = ind.RDB$RELATION_NAME "
			" and rfl.RDB$FIELD_NAME = seg.RDB$FIELD_NAME "
			"join RDB$FIELDS fld on rfl.RDB$FIELD_SOURCE = fld.RDB$FIELD_NAME "
			"join RDB$COLLATIONS coll on fld.RDB$CHARACTER_SET_ID = coll.RDB$CHARACTER_SET_ID "
			" and coalesce(rfl.RDB$COLLATION_ID, fld.RDB$COLLATION_ID) = coll.RDB$COLLATION_ID "
			"join RDB$CHARACTER_SETS cs on coll.RDB$CHARACTER_SET_ID = cs.RDB$CHARACTER_SET_ID "
			"where coll.RDB$SPECIFIC_ATTRIBUTES like '%COLL-VERSION=%' "
			" and coalesce(ind.RDB$INDEX_INACTIVE, 0) = 0 "
			" and coalesce(rel.RDB$RELATION_TYPE, 0) = 0 "		// rel_persistent
			"group by ind.RDB$INDEX_NAME, rel.RDB$RELATION_ID, coll.RDB$BASE_COLLATION_NAME, "
			" coll.RDB$COLLATION_NAME, cs.RDB$CHARACTER_SET_NAME, coll.RDB$SPECIFIC_ATTRIBUTES";

		{	// scope
			AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, indSql));
			AutoResultSet rs(ps->executeQuery(tdbb, transaction));
			while(rs->fetch(tdbb))
			{
				const MetaName collationName(rs->getMetaName(tdbb, 3));
				const MetaName charsetName(rs->getMetaName(tdbb, 4));
				const string specificAttributes(rs->getString(tdbb, 5));
				string newSpecificAttributes;
				if (!IntlManager::setupCollationAttributes(collationName.c_str(),
						charsetName.c_str(), specificAttributes, newSpecificAttributes))
				{
					DEBUG_REBUILD_INTL(fprintf(stderr, "setupCollationAttributes failed\n"));
					continue;
				}
				if (newSpecificAttributes == specificAttributes)
					continue;

				MetaName t(rs->getMetaName(tdbb, 1));
				if (!indices.exist(t))
					indices.add(rs->getMetaName(tdbb, 1));

				USHORT rel_id = rs->getInt(tdbb, 2);
				if (!tables.exists(rel_id))
				{
					jrd_rel* relation = MET_lookup_relation_id(tdbb, rel_id, false);
					if (relation)
						tables.addRelation(relation);
				}
			}
		}

		// Lock the tables
		tables.lock();

		// Change collation's attributes
		const char* collSql =
			"select coll.RDB$COLLATION_NAME, coll.RDB$CHARACTER_SET_ID from RDB$COLLATIONS coll "
			"where coll.RDB$SPECIFIC_ATTRIBUTES like '%COLL-VERSION=%'";
		{	// scope
			AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, collSql));
			AutoResultSet rs(ps->executeQuery(tdbb, transaction));
			while(rs->fetch(tdbb))
			{
				MetaName collName(rs->getMetaName(tdbb, 1));
				const USHORT charSetId(rs->getSmallInt(tdbb, 2));

				setupSpecificCollationAttributes(tdbb, transaction, charSetId, collName.c_str(), true);
			}
		}

		// Reactivate indices
		{	// scope
			for (MetaName* idx = indices.begin(); idx != indices.end(); ++idx)
			{
				AutoRequest request;

				FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
					IDX IN RDB$INDICES
					WITH IDX.RDB$INDEX_NAME EQ idx->c_str()
				{
					MODIFY IDX
						DEBUG_REBUILD_INTL(fprintf(stderr, "Re-activate index %s\n", idx->c_str()));
						IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
						IDX.RDB$INDEX_INACTIVE = FALSE;
					END_MODIFY
				}
				END_FOR
			}
		}

		// Commit
		TRA_commit(tdbb, transaction, false);
		transaction = NULL;
		tdbb->setTransaction(oldTransaction);
	}
	catch (const Firebird::Exception&)
	{
		if (transaction)
		{
			TRA_rollback(tdbb, transaction, false, true);
		}
		tdbb->setTransaction(oldTransaction);

		throw;
	}
}


static bool delete_collation(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ c o l l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Check if it is allowable to delete
 *	a collation, and if so, clean up after it.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
		check_dependencies(tdbb, work->dfw_name.c_str(), NULL, NULL, obj_collation, transaction);
		return true;

	case 2:
		return true;

	case 3:
		INTL_texttype_unload(tdbb, work->dfw_id);
		break;
	}

	return false;
}


static bool delete_exception(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ e x c e p t i o n
 *
 **************************************
 *
 * Functional description
 *	Check if it is allowable to delete
 *	an exception, and if so, clean up after it.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
		check_dependencies(tdbb, work->dfw_name.c_str(), NULL, NULL, obj_exception, transaction);
		break;
	}

	return false;
}


static bool set_generator(thread_db* tdbb,
						  SSHORT phase,
						  DeferredWork* work,
						  jrd_tra* transaction)
{
/**************************************
 *
 *	s e t _ g e n e r a t o r
 *
 **************************************
 *
 * Functional description
 *	Set the generator to the given value.
 *
 **************************************/
	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			const SLONG id = MET_lookup_generator(tdbb, work->dfw_name);
			if (id >= 0)
			{
				fb_assert(id == work->dfw_id);
				SINT64 value = 0;
				if (transaction->getGenIdCache()->get(id, value))
				{
					transaction->getGenIdCache()->remove(id);
					DPM_gen_id(tdbb, id, true, value);
				}
			}
#ifdef DEV_BUILD
			else // This is a test only
				status_exception::raise(Arg::Gds(isc_cant_modify_sysobj) << "generator" << work->dfw_name);
#endif
		}
		break;
	}

	return false;
}


static bool delete_generator(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ g e n e r a t o r
 *
 **************************************
 *
 * Functional description
 *	Check if it is allowable to delete
 *	a generator, and if so, clean up after it.
 * CVC: This function was modelled after delete_exception.
 *
 **************************************/

	SET_TDBB(tdbb);
	const char* gen_name = work->dfw_name.c_str();

	switch (phase)
	{
	case 1:
		check_dependencies(tdbb, gen_name, NULL, NULL, obj_generator, transaction);
		break;
	}

	return false;
}


static bool create_field(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	c r e a t e _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	Store dependencies of a field.
 *
 **************************************/

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

	switch (phase)
	{
	case 1:
		{
			const MetaName depName(work->dfw_name);
			AutoRequest handle;
			bid validation;
			validation.clear();

			FOR(REQUEST_HANDLE handle)
				FLD IN RDB$FIELDS WITH
					FLD.RDB$FIELD_NAME EQ depName.c_str()
			{
				if (!FLD.RDB$VALIDATION_BLR.NULL)
					validation = FLD.RDB$VALIDATION_BLR;
			}
			END_FOR

			if (!validation.isEmpty())
			{
				MemoryPool* new_pool = attachment->createPool();
				Jrd::ContextPoolHolder context(tdbb, new_pool);

				MET_get_dependencies(tdbb, NULL, NULL, 0, NULL, &validation,
					NULL, NULL, depName, obj_validation, 0, transaction, depName);

				attachment->deletePool(new_pool);
			}
		}
		// fall through

	case 2:
	case 3:
		return true;

	case 4:	// after scan_relation (phase 3)
		check_computed_dependencies(tdbb, transaction, work->dfw_name);
		break;
	}

	return false;
}


static bool delete_field(thread_db*	tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	This whole routine exists just to
 *	return an error if someone attempts to
 *	delete a global field that is in use
 *
 **************************************/

	int field_count;
	AutoRequest handle;

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

	switch (phase)
	{
	case 1:
		// Look up the field in RFR.  If we can't find the field, go ahead with the delete.

		handle.reset();
		field_count = 0;

		FOR(REQUEST_HANDLE handle)
			RFR IN RDB$RELATION_FIELDS CROSS
				REL IN RDB$RELATIONS
				OVER RDB$RELATION_NAME
				WITH RFR.RDB$FIELD_SOURCE EQ work->dfw_name.c_str()
		{
			// If the rfr field is also being deleted, there's no dependency
			if (!find_depend_in_dfw(tdbb, RFR.RDB$FIELD_NAME, obj_computed, REL.RDB$RELATION_ID,
									transaction))
			{
				field_count++;
			}
		}
		END_FOR

		if (field_count)
		{
			ERR_post(Arg::Gds(isc_no_meta_update) <<
					 Arg::Gds(isc_no_delete) <<								// Msg353: can not delete
					 Arg::Gds(isc_domain_name) << Arg::Str(work->dfw_name) <<
					 Arg::Gds(isc_dependency) << Arg::Num(field_count)); 	// Msg310: there are %ld dependencies
		}

		check_dependencies(tdbb, work->dfw_name.c_str(), NULL, NULL, obj_field, transaction);

	case 2:
		return true;

	case 3:
		MET_delete_dependencies(tdbb, work->dfw_name, obj_computed, transaction);
		MET_delete_dependencies(tdbb, work->dfw_name, obj_validation, transaction);
		break;
	}

	return false;
}


static bool modify_field(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	m o d i f y _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	Handle constraint dependencies of a field.
 *
 **************************************/

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

	switch (phase)
	{
	case 1:
		{
			const MetaName depName(work->dfw_name);
			AutoRequest handle;

			// If a domain is being changed to NOT NULL, schedule validation of involved relations.
			if (work->findArg(dfw_arg_field_not_null))
			{
				FOR(REQUEST_HANDLE handle)
					RFL IN RDB$RELATION_FIELDS CROSS
					REL IN RDB$RELATIONS
					WITH REL.RDB$RELATION_NAME EQ RFL.RDB$RELATION_NAME AND
						 RFL.RDB$FIELD_SOURCE EQ depName.c_str() AND
						 (RFL.RDB$NULL_FLAG MISSING OR RFL.RDB$NULL_FLAG = FALSE) AND
						 REL.RDB$VIEW_BLR MISSING
					REDUCED TO RFL.RDB$RELATION_NAME, RFL.RDB$FIELD_ID
				{
					dsc desc;
					desc.makeText(static_cast<USHORT>(strlen(RFL.RDB$RELATION_NAME)), CS_METADATA,
						(UCHAR*) RFL.RDB$RELATION_NAME);

					DeferredWork* work = DFW_post_work(transaction, dfw_check_not_null, &desc, 0);
					SortedArray<int>& ids = DFW_get_ids(work);

					FB_SIZE_T pos;
					if (!ids.find(RFL.RDB$FIELD_ID, pos))
						ids.insert(pos, RFL.RDB$FIELD_ID);
				}
				END_FOR
			}

			bid validation;
			validation.clear();

			handle.reset();

			FOR(REQUEST_HANDLE handle)
				FLD IN RDB$FIELDS WITH
					FLD.RDB$FIELD_NAME EQ depName.c_str()
			{
				if (!FLD.RDB$VALIDATION_BLR.NULL)
					validation = FLD.RDB$VALIDATION_BLR;
			}
			END_FOR

			const DeferredWork* const arg = work->findArg(dfw_arg_new_name);

			// ASF: If there are procedures depending on the domain, it can't be renamed.
			if (arg && depName != arg->dfw_name.c_str())
				check_dependencies(tdbb, depName.c_str(), NULL, NULL, obj_field, transaction);

			MET_delete_dependencies(tdbb, depName, obj_validation, transaction);

			if (!validation.isEmpty())
			{
				MemoryPool* new_pool = attachment->createPool();
				Jrd::ContextPoolHolder context(tdbb, new_pool);

				MET_get_dependencies(tdbb, NULL, NULL, 0, NULL, &validation,
					NULL, NULL, depName, obj_validation, 0, transaction, depName);

				attachment->deletePool(new_pool);
			}
		}
		// fall through

	case 2:
	case 3:
		return true;

	case 4:	// after scan_relation (phase 3)
		check_computed_dependencies(tdbb, transaction, work->dfw_name);
		break;
	}

	return false;
}


static bool delete_global(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ g l o b a l
 *
 **************************************
 *
 * Functional description
 *	If a local field has been deleted,
 *	check to see if its global field
 *	is computed. If so, delete all its
 *	dependencies under the assumption
 *	that a global computed field has only
 *	one local field.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			AutoRequest handle;
			FOR(REQUEST_HANDLE handle)
				FLD IN RDB$FIELDS WITH
					FLD.RDB$FIELD_NAME EQ work->dfw_name.c_str() AND
					FLD.RDB$COMPUTED_BLR NOT MISSING
			{
				MET_delete_dependencies(tdbb, work->dfw_name, obj_computed, transaction);
			}
			END_FOR
		}
		break;
	}

	return false;
}


static void check_partners(thread_db* tdbb, const USHORT rel_id)
{
/**************************************
 *
 *	c h e c k _ p a r t n e r s
 *
 **************************************
 *
 * Functional description
 *	Signal other processes to check partners of relation rel_id
 * Used when FK index was dropped
 *
 **************************************/
	const Jrd::Attachment* att = tdbb->getAttachment();
	vec<jrd_rel*>* relations = att->att_relations;

	fb_assert(relations);
	fb_assert(rel_id < relations->count());

	jrd_rel *relation = (*relations)[rel_id];
	fb_assert(relation);

	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 bool delete_index(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ i n d e x
 *
 **************************************
 *
 * Functional description
 *
 **************************************/
	IndexLock* index = NULL;

	SET_TDBB(tdbb);

	const DeferredWork* arg = work->findArg(dfw_arg_index_name);

	fb_assert(arg);
	fb_assert(arg->dfw_id > 0);
	const USHORT id = arg->dfw_id - 1;
	// Maybe a permanent check?
	//if (id == idx_invalid)
	//	ERR_post(...);

	// Look up the relation.  If we can't find the relation,
	// don't worry about the index.

	jrd_rel* relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
	if (!relation) {
		return false;
	}

	RelationPages* relPages = relation->getPages(tdbb, MAX_TRA_NUMBER, false);
	if (!relPages) {
		return false;
	}
	// we need to special handle temp tables with ON PRESERVE ROWS only
	const bool isTempIndex = (relation->rel_flags & REL_temp_conn) &&
		(relPages->rel_instance_id != 0);

	switch (phase)
	{
	case 0:
		index = CMP_get_index_lock(tdbb, relation, id);
		if (index)
		{
			if (!index->idl_count)
				LCK_release(tdbb, index->idl_lock);
		}
		return false;

	case 1:
		check_dependencies(tdbb, arg->dfw_name.c_str(), NULL, NULL, obj_index, transaction);
		return true;

	case 2:
		return true;

	case 3:
		// Make sure nobody is currently using the index

		// If we about to delete temp index instance then usage counter
		// will remains 1 and will be decremented by IDX_delete_index at
		// phase 4

		index = CMP_get_index_lock(tdbb, relation, id);
		if (index)
		{
			// take into account lock probably used by temp index instance
			bool temp_lock_released = false;
			if (isTempIndex && (index->idl_count == 1))
			{
				index_desc idx;
				if (BTR_lookup(tdbb, relation, id, &idx, relPages))
				{
					index->idl_count--;
					LCK_release(tdbb, index->idl_lock);
					temp_lock_released = true;
				}
			}

			// Try to clear trigger cache to release lock
			if (index->idl_count)
				MET_clear_cache(tdbb);

			if (!isTempIndex)
			{
				if (index->idl_count ||
					!LCK_lock(tdbb, index->idl_lock, LCK_EX, transaction->getLockWait()))
				{
					// restore lock used by temp index instance
					if (temp_lock_released)
					{
						LCK_lock(tdbb, index->idl_lock, LCK_SR, LCK_WAIT);
						index->idl_count++;
					}

					raiseObjectInUseError("INDEX", arg->dfw_name);
				}
				index->idl_count++;
			}
		}

		return true;

	case 4:
		index = CMP_get_index_lock(tdbb, relation, id);
		if (isTempIndex && index)
			index->idl_count++;
		IDX_delete_index(tdbb, relation, id);

		if (isTempIndex)
			return false;

		if (work->dfw_type == dfw_delete_expression_index)
		{
			MET_delete_dependencies(tdbb, arg->dfw_name, obj_expression_index, transaction);
		}

		// if index was bound to deleted FK constraint
		// then work->dfw_args was set in VIO_erase
		arg = work->findArg(dfw_arg_partner_rel_id);

		if (arg) {
			if (arg->dfw_id) {
				check_partners(tdbb, relation->rel_id);
				if (relation->rel_id != arg->dfw_id) {
					check_partners(tdbb, arg->dfw_id);
				}
			}
			else {
				// partner relation was not found in VIO_erase
				// we must check partners of all relations in database
				MET_update_partners(tdbb);
			}
		}

		if (index)
		{
			/* in order for us to have gotten the lock in phase 3
			* idl_count HAD to be 0, therefore after having incremented
			* it for the exclusive lock it would have to be 1.
			* IF now it is NOT 1 then someone else got a lock to
			* the index and something is seriously wrong */
			fb_assert(index->idl_count == 1);
			if (!--index->idl_count)
			{
				// Release index existence lock and memory.

				for (IndexLock** ptr = &relation->rel_index_locks; *ptr; ptr = &(*ptr)->idl_next)
				{
					if (*ptr == index)
					{
						*ptr = index->idl_next;
						break;
					}
				}
				if (index->idl_lock)
				{
					LCK_release(tdbb, index->idl_lock);
					delete index->idl_lock;
				}
				delete index;

				// Release index refresh lock and memory.

				for (IndexBlock** iptr = &relation->rel_index_blocks; *iptr; iptr = &(*iptr)->idb_next)
				{
					if ((*iptr)->idb_id == id)
					{
						IndexBlock* index_block = *iptr;
						*iptr = index_block->idb_next;

						// Lock was released in IDX_delete_index().

						delete index_block->idb_lock;
						delete index_block;
						break;
					}
				}
			}
		}
		break;
	}

	return false;
}


static bool delete_parameter(thread_db* tdbb, SSHORT phase, DeferredWork*, jrd_tra*)
{
/**************************************
 *
 *	d e l e t e _ p a r a m e t e r
 *
 **************************************
 *
 * Functional description
 *	Return an error if someone attempts to
 *	delete a field from a procedure and it is
 *	used by a view or procedure.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
		/* hvlad: temporary disable procedure parameters dependency check
		  until proper solution (something like dyn_mod_parameter)
		  will be implemented. This check never worked properly
		  so no harm is done

		if (MET_lookup_procedure_id(tdbb, work->dfw_id, false, true, 0))
		{
			const DeferredWork* arg = work->dfw_args;
			fb_assert(arg && (arg->dfw_type == dfw_arg_proc_name));

			check_dependencies(tdbb, arg->dfw_name.c_str(), work->dfw_name.c_str(),
							   obj_procedure, transaction);
		}
		*/
		break;
	}

	return false;
}


static bool delete_relation(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Check if it is allowable to delete
 *	a relation, and if so, clean up after it.
 *
 **************************************/
	AutoRequest request;
	jrd_rel* relation;
	Resource* rsc;
	USHORT view_count;
	bool adjusted;

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

	switch (phase)
	{
	case 0:
		relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
		if (!relation) {
			return false;
		}

		if (relation->rel_existence_lock)
		{
			LCK_convert(tdbb, relation->rel_existence_lock, LCK_SR, transaction->getLockWait());
		}

		if (relation->rel_flags & REL_deleting)
		{
			relation->rel_flags &= ~REL_deleting;
			relation->rel_drop_mutex.leave();
		}

		return false;

	case 1:
		// check if any views use this as a base relation

		request.reset();
		view_count = 0;
		FOR(REQUEST_HANDLE request)
			X IN RDB$VIEW_RELATIONS WITH
			X.RDB$RELATION_NAME EQ work->dfw_name.c_str()
		{
			// If the view is also being deleted, there's no dependency
			if (!find_depend_in_dfw(tdbb, X.RDB$VIEW_NAME, obj_view, 0, transaction))
			{
				view_count++;
			}
		}
		END_FOR

		if (view_count)
		{
			ERR_post(Arg::Gds(isc_no_meta_update) <<
					 Arg::Gds(isc_no_delete) << // Msg353: can not delete
					 Arg::Gds(isc_table_name) << Arg::Str(work->dfw_name) <<
					 Arg::Gds(isc_dependency) << Arg::Num(view_count));
					 // Msg310: there are %ld dependencies
		}

		relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
		if (!relation)
			return false;

		check_dependencies(tdbb, work->dfw_name.c_str(), NULL, NULL,
			(relation->isView() ? obj_view : obj_relation), transaction);
		return true;

	case 2:
		relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
		if (!relation) {
			return false;
		}

		// Let relation be deleted if only this transaction is using it

		adjusted = false;
		if (relation->rel_use_count == 1)
		{
			for (rsc = transaction->tra_resources.begin(); rsc < transaction->tra_resources.end();
				rsc++)
			{
				if (rsc->rsc_rel == relation)
				{
					--relation->rel_use_count;
					adjusted = true;
					break;
				}
			}
		}

		if (relation->rel_use_count)
			MET_clear_cache(tdbb);

		if (relation->rel_use_count || (relation->rel_existence_lock &&
			!LCK_convert(tdbb, relation->rel_existence_lock, LCK_EX, transaction->getLockWait())))
		{
			if (adjusted)
				++relation->rel_use_count;

			raiseRelationInUseError(relation);
		}

		fb_assert(!relation->rel_use_count);

		// Flag relation delete in progress so that active sweep or
		// garbage collector threads working on relation can skip over it

		relation->rel_flags |= REL_deleting;
		{ // scope
			EngineCheckout cout(tdbb, FB_FUNCTION);
			relation->rel_drop_mutex.enter(FB_FUNCTION);
		}

		return true;

	case 3:
		return true;

	case 4:
		relation = MET_lookup_relation_id(tdbb, work->dfw_id, true);
		if (!relation) {
			fb_assert(false);
			return false;
		}

		// The sweep and garbage collector threads have no more than
		// a single record latency in responding to the flagged relation
		// deletion. Nevertheless, as a defensive programming measure,
		// don't wait forever if something has gone awry and the sweep
		// count doesn't run down.

		for (int wait = 0; wait < 60; wait++)
		{
			if (!relation->rel_sweep_count) {
				break;
			}

			EngineCheckout cout(tdbb, FB_FUNCTION);
			Thread::sleep(1 * 1000);
		}

		if (relation->rel_sweep_count)
			raiseRelationInUseError(relation);

		// Free any memory associated with the relation's garbage collection bitmap
		if (dbb->dbb_garbage_collector) {
			dbb->dbb_garbage_collector->removeRelation(relation->rel_id);
		}

		if (relation->rel_file) {
		    EXT_fini(relation, false);
		}

		if (relation->isTemporary())
		{
			// release pages, allocated for current GTT instance
			AutoSetRestoreFlag<ULONG> tmpSpace(&tdbb->tdbb_flags, TDBB_use_db_page_space, false);
			relation->delPages(tdbb);
		}

		RelationPages* const relPages = relation->getBasePages();
		if (relPages->rel_index_root) {
			IDX_delete_indices(tdbb, relation, relPages);
		}

		if (relPages->rel_pages) {
			DPM_delete_relation(tdbb, relation);
		}

		// if this is a view (or even if we don't know), delete dependency lists

		if (relation->rel_view_rse || !(relation->rel_flags & REL_scanned)) {
			MET_delete_dependencies(tdbb, work->dfw_name, obj_view, transaction);
		}

		// Now that the data, pointer, and index pages are gone,
		// get rid of the relation itself

		request.reset();

		FOR(REQUEST_HANDLE request) X IN RDB$FORMATS WITH
			X.RDB$RELATION_ID EQ relation->rel_id
		{
			ERASE X;
		}
		END_FOR

		// Release relation locks
		if (relation->rel_existence_lock) {
			LCK_release(tdbb, relation->rel_existence_lock);
		}
		if (relation->rel_partners_lock) {
			LCK_release(tdbb, relation->rel_partners_lock);
		}
		if (relation->rel_rescan_lock) {
			LCK_release(tdbb, relation->rel_rescan_lock);
		}

		// Mark relation in the cache as dropped
		relation->rel_flags |= REL_deleted;

		if (relation->rel_flags & REL_deleting)
		{
			relation->rel_flags &= ~REL_deleting;
			relation->rel_drop_mutex.leave();
		}

		// Release relation triggers
		relation->releaseTriggers(tdbb, true);

		break;
	}

	return false;
}


static bool delete_rfr(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ r f r
 *
 **************************************
 *
 * Functional description
 *	This whole routine exists just to
 *	return an error if someone attempts to
 *	1. delete a field from a relation if the relation
 *		is used in a view and the field is referenced in
 *		the view.
 *	2. drop the last column of a table
 *
 **************************************/
	int rel_exists, field_count;
	AutoRequest handle;
	MetaName f;
	jrd_rel* relation;

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

	switch (phase)
	{
	case 1:
		// first check if there are any fields used explicitly by the view

		handle.reset();
		field_count = 0;
		FOR(REQUEST_HANDLE handle)
			REL IN RDB$RELATIONS CROSS
				VR IN RDB$VIEW_RELATIONS OVER RDB$RELATION_NAME CROSS
				VFLD IN RDB$RELATION_FIELDS WITH
				REL.RDB$RELATION_ID EQ work->dfw_id AND
				VFLD.RDB$VIEW_CONTEXT EQ VR.RDB$VIEW_CONTEXT AND
				VFLD.RDB$RELATION_NAME EQ VR.RDB$VIEW_NAME AND
				VFLD.RDB$BASE_FIELD EQ work->dfw_name.c_str()
		{
			// If the view is also being deleted, there's no dependency
			if (!find_depend_in_dfw(tdbb, VR.RDB$VIEW_NAME, obj_view, 0, transaction))
			{
				f = VFLD.RDB$BASE_FIELD;
				field_count++;
			}
		}
		END_FOR

		if (field_count)
		{
			ERR_post(Arg::Gds(isc_no_meta_update) <<
					 Arg::Gds(isc_no_delete) <<	// Msg353: can not delete
					 Arg::Gds(isc_field_name) << Arg::Str(f) <<
					 Arg::Gds(isc_dependency) << Arg::Num(field_count));
					 // Msg310: there are %ld dependencies
		}

		// now check if there are any dependencies generated through the blr
		// that defines the relation

		if ( (relation = MET_lookup_relation_id(tdbb, work->dfw_id, false)) )
		{
			check_dependencies(tdbb, relation->rel_name.c_str(), work->dfw_name.c_str(), NULL,
							   (relation->isView() ? obj_view : obj_relation),
							   transaction);
		}

		// see if the relation itself is being dropped

		handle.reset();
		rel_exists = 0;
		FOR(REQUEST_HANDLE handle)
			REL IN RDB$RELATIONS WITH REL.RDB$RELATION_ID EQ work->dfw_id
		{
			rel_exists++;
		}
		END_FOR

		// if table exists, check if this is the last column in the table

		if (rel_exists)
		{
			field_count = 0;
			handle.reset();

			FOR(REQUEST_HANDLE handle)
				REL IN RDB$RELATIONS CROSS
					RFLD IN RDB$RELATION_FIELDS OVER RDB$RELATION_NAME
					WITH REL.RDB$RELATION_ID EQ work->dfw_id
				field_count++;
			END_FOR

			if (!field_count)
			{
				ERR_post(Arg::Gds(isc_no_meta_update) <<
						 Arg::Gds(isc_del_last_field));
				// Msg354: last column in a relation cannot be deleted
			}
		}

	case 2:
		return true;

	case 3:
		// Unlink field from data structures.  Don't try to actually release field and
		// friends -- somebody may be pointing to them

		relation = MET_lookup_relation_id(tdbb, work->dfw_id, false);
		if (relation)
		{
			const int id = MET_lookup_field(tdbb, relation, work->dfw_name);
			if (id >= 0)
			{
				vec<jrd_fld*>* vector = relation->rel_fields;
				if (vector && (ULONG) id < vector->count() && (*vector)[id])
				{
					(*vector)[id] = NULL;
				}
			}
		}
		break;
	}

	return false;
}


static bool delete_shadow(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	d e l e t e _ s h a d o w
 *
 **************************************
 *
 * Functional description
 *	Provide deferred work interface to
 *	MET_delete_shadow.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		MET_delete_shadow(tdbb, work->dfw_id);
		break;
	}

	return false;
}


static bool delete_trigger(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	d e l e t e _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *	Cleanup after a deleted trigger.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		// get rid of dependencies
		MET_delete_dependencies(tdbb, work->dfw_name, obj_trigger, transaction);
		return true;

	case 4:
		{
			const DeferredWork* arg = work->findArg(dfw_arg_rel_name);
			if (!arg)
			{
				const DeferredWork* arg = work->findArg(dfw_arg_trg_type);
				fb_assert(arg);

				// ASF: arg->dfw_id is RDB$TRIGGER_TYPE truncated to USHORT
				if (arg)
				{
					if ((arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB)
					{
						MET_release_trigger(tdbb,
							&tdbb->getAttachment()->att_triggers[arg->dfw_id & ~TRIGGER_TYPE_DB],
							work->dfw_name);
					}
					else if ((arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DDL)
					{
						MET_release_trigger(tdbb,
							&tdbb->getAttachment()->att_ddl_triggers,
							work->dfw_name);
					}
				}
			}
		}
		break;
	}

	return false;
}


static bool find_depend_in_dfw(thread_db*	tdbb,
							   TEXT*	object_name,
							   USHORT	dep_type,
							   USHORT	rel_id,
							   jrd_tra*		transaction)
{
/**************************************
 *
 *	f i n d _ d e p e n d _ i n _ d f w
 *
 **************************************
 *
 * Functional description
 *	Check the object to see if it is being
 *	deleted as part of the deferred work.
 *	Return true if it is, false otherwise.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	fb_utils::exact_name(object_name);
	enum dfw_t dfw_type;
	switch (dep_type)
	{
	case obj_view:
		dfw_type = dfw_delete_relation;
		break;
	case obj_trigger:
		dfw_type = dfw_delete_trigger;
		break;
	case obj_computed:
		dfw_type = rel_id ? dfw_delete_rfr : dfw_delete_global;
		break;
	case obj_validation:
		dfw_type = dfw_delete_global;
		break;
	case obj_procedure:
		dfw_type = dfw_delete_procedure;
		break;
	case obj_expression_index:
		dfw_type = dfw_delete_expression_index;
		break;
	case obj_package_header:
		dfw_type = dfw_drop_package_header;
		break;
	case obj_package_body:
		dfw_type = dfw_drop_package_body;
		break;
	case obj_udf:
		dfw_type = dfw_delete_function;
		break;
	default:
		fb_assert(false);
		break;
	}

	// Look to see if an object of the desired type is being deleted or modified.
	// For an object being modified we verify dependencies separately when we parse its BLR.
	for (const DeferredWork* work = transaction->tra_deferred_job->work; work; work = work->getNext())
	{
		if ((work->dfw_type == dfw_type ||
			(work->dfw_type == dfw_modify_procedure && dfw_type == dfw_delete_procedure) ||
			(work->dfw_type == dfw_modify_field && dfw_type == dfw_delete_global) ||
			(work->dfw_type == dfw_modify_trigger && dfw_type == dfw_delete_trigger) ||
			(work->dfw_type == dfw_modify_function && dfw_type == dfw_delete_function)) &&
			work->dfw_name == object_name && work->dfw_package.isEmpty() &&
			(!rel_id || rel_id == work->dfw_id))
		{
			if (work->dfw_type == dfw_modify_procedure || work->dfw_type == dfw_modify_function)
			{
				// Don't consider that routine is in DFW if we are only checking the BLR
				if (!work->findArg(dfw_arg_check_blr))
					return true;
			}
			else
			{
				return true;
			}
		}

		if (work->dfw_type == dfw_type && dfw_type == dfw_delete_expression_index)
		{
			for (FB_SIZE_T i = 0; i < work->dfw_args.getCount(); ++i)
			{
				const DeferredWork* arg = work->dfw_args[i];
				if (arg->dfw_type == dfw_arg_index_name &&
					arg->dfw_name == object_name)
				{
					return true;
				}
			}
		}
	}

	if (dfw_type == dfw_delete_global)
	{
		if (dep_type == obj_computed)
		{
			// Computed fields are more complicated.  If the global field isn't being
			// deleted, see if all of the fields it is the source for, are.

			AutoCacheRequest request(tdbb, irq_ch_cmp_dpd, IRQ_REQUESTS);

			FOR(REQUEST_HANDLE request)
				FLD IN RDB$FIELDS CROSS
					RFR IN RDB$RELATION_FIELDS CROSS
					REL IN RDB$RELATIONS
					WITH FLD.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE
					AND FLD.RDB$FIELD_NAME EQ object_name
					AND REL.RDB$RELATION_NAME EQ RFR.RDB$RELATION_NAME
			{
				if (!find_depend_in_dfw(tdbb, RFR.RDB$FIELD_NAME, obj_computed,
										REL.RDB$RELATION_ID, transaction))
				{
					return false;
				}
			}
			END_FOR

			return true;
		}

		if (dep_type == obj_validation)
		{
			// Maybe it's worth caching in the future?
			AutoRequest request;

			FOR(REQUEST_HANDLE request)
				FLD IN RDB$FIELDS WITH
					FLD.RDB$FIELD_NAME EQ object_name
			{
				if (!FLD.RDB$VALIDATION_BLR.NULL)
					return false;
			}
			END_FOR

			return true;
		}
	}

	return false;
}


static void get_array_desc(thread_db* tdbb, const TEXT* field_name, Ods::InternalArrayDesc* desc)
{
/**************************************
 *
 *	g e t _ a r r a y _ d e s c
 *
 **************************************
 *
 * Functional description
 *	Get array descriptor for an array.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	AutoCacheRequest request(tdbb, irq_r_fld_dim, IRQ_REQUESTS);

	Ods::InternalArrayDesc::iad_repeat* ranges = 0;
	FOR (REQUEST_HANDLE request)
		D IN RDB$FIELD_DIMENSIONS WITH D.RDB$FIELD_NAME EQ field_name
	{
		if (D.RDB$DIMENSION >= 0 && D.RDB$DIMENSION < desc->iad_dimensions)
		{
			ranges = desc->iad_rpt + D.RDB$DIMENSION;
			ranges->iad_lower = D.RDB$LOWER_BOUND;
			ranges->iad_upper = D.RDB$UPPER_BOUND;
		}
	}
	END_FOR

	desc->iad_count = 1;

	for (ranges = desc->iad_rpt + desc->iad_dimensions; --ranges >= desc->iad_rpt;)
	{
		ranges->iad_length = desc->iad_count;
		desc->iad_count *= ranges->iad_upper - ranges->iad_lower + 1;
	}

	desc->iad_version = Ods::IAD_VERSION_1;
	desc->iad_length = IAD_LEN(MAX(desc->iad_struct_count, desc->iad_dimensions));
	desc->iad_element_length = desc->iad_rpt[0].iad_desc.dsc_length;
	desc->iad_total_length = desc->iad_element_length * desc->iad_count;
}


static void get_trigger_dependencies(DeferredWork* work, bool compile, jrd_tra* transaction)
{
/**************************************
 *
 *	g e t _ t r i g g e r _ d e p e n d e n c i e s
 *
 **************************************
 *
 * Functional description
 *	Get relations and fields on which this
 *	trigger depends, either when it's being
 *	created or when it's modified.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	Jrd::Attachment* attachment = tdbb->getAttachment();

	if (compile)
		compile = !tdbb->getAttachment()->isGbak();

	jrd_rel* relation = NULL;
	bid blob_id;
	blob_id.clear();

	ISC_UINT64 type = 0;

	AutoCacheRequest handle(tdbb, irq_c_trigger, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle)
		X IN RDB$TRIGGERS WITH
			X.RDB$TRIGGER_NAME EQ work->dfw_name.c_str()
	{
		blob_id = X.RDB$TRIGGER_BLR;
		type = (ISC_UINT64) X.RDB$TRIGGER_TYPE;
		relation = MET_lookup_relation(tdbb, X.RDB$RELATION_NAME);
	}
	END_FOR

	// get any dependencies now by parsing the blr

	if ((relation || (type & TRIGGER_TYPE_MASK) != TRIGGER_TYPE_DML) && !blob_id.isEmpty())
	{
		JrdStatement* statement = NULL;
		// Nickolay Samofatov: allocate statement memory pool...
		MemoryPool* new_pool = attachment->createPool();
		USHORT par_flags;

		if ((type & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DML)
			par_flags = (USHORT) ((type & 1) ? csb_pre_trigger : csb_post_trigger);
		else
			par_flags = 0;

		Jrd::ContextPoolHolder context(tdbb, new_pool);
		const MetaName depName(work->dfw_name);
		MET_get_dependencies(tdbb, relation, NULL, 0, NULL, &blob_id, (compile ? &statement : NULL),
							 NULL, depName, obj_trigger, par_flags, transaction);

		if (statement)
			statement->release(tdbb);
		else
			attachment->deletePool(new_pool);
	}
}


static Format* make_format(thread_db* tdbb, jrd_rel* relation, USHORT* version, TemporaryField* stack)
{
/**************************************
 *
 *	m a k e _ f o r m a t
 *
 **************************************
 *
 * Functional description
 *	Make a format block for a relation.
 *
 **************************************/
	TemporaryField* tfb;

	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();
	jrd_tra* sysTransaction = attachment->getSysTransaction();
	Database* dbb = tdbb->getDatabase();

	// Figure out the highest field id and allocate a format block

	USHORT count = 0;
	for (tfb = stack; tfb; tfb = tfb->tfb_next)
		count = MAX(count, tfb->tfb_id);

	Format* format = Format::newFormat(*relation->rel_pool, count + 1);
	format->fmt_version = version ? *version : 0;

	// Fill in the format block from the temporary field blocks

	for (tfb = stack; tfb; tfb = tfb->tfb_next)
	{
		dsc* desc = &format->fmt_desc[tfb->tfb_id];
		if (tfb->tfb_flags & TFB_array)
		{
			desc->dsc_dtype = dtype_array;
			desc->dsc_length = sizeof(ISC_QUAD);
		}
		else
			*desc = tfb->tfb_desc;
		if (tfb->tfb_flags & TFB_computed)
			desc->dsc_dtype |= COMPUTED_FLAG;

		impure_value& defRef = format->fmt_defaults[tfb->tfb_id];
		defRef = tfb->tfb_default;

		if (tfb->tfb_default.vlu_string)
		{
			fb_assert(defRef.vlu_desc.dsc_dtype == dtype_text);
			defRef.vlu_desc.dsc_address = defRef.vlu_string->str_data;
		}
		else
			defRef.vlu_desc.dsc_address = (UCHAR*) &defRef.vlu_misc;
	}

	// Compute the offsets of the various fields

	ULONG offset = FLAG_BYTES(count);

	count = 0;
	for (Format::fmt_desc_iterator desc2 = format->fmt_desc.begin();
		count < format->fmt_count;
		++count, ++desc2)
	{
		if (desc2->dsc_dtype & COMPUTED_FLAG)
		{
			desc2->dsc_dtype &= ~COMPUTED_FLAG;
			continue;
		}
		if (desc2->dsc_dtype)
		{
			offset = MET_align(&(*desc2), offset);
			desc2->dsc_address = (UCHAR *) (IPTR) offset;
			offset += desc2->dsc_length;
		}
	}

	// Release the temporary field blocks

	while ( (tfb = stack) )
	{
		stack = tfb->tfb_next;
		delete tfb;
	}

	if (offset > MAX_RECORD_SIZE)
	{
		delete format;
		ERR_post(Arg::Gds(isc_no_meta_update) <<
				 Arg::Gds(isc_rec_size_err) << Arg::Num(offset) <<
				 Arg::Gds(isc_table_name) << Arg::Str(relation->rel_name));
		// Msg361: new record size of %ld bytes is too big
	}

	format->fmt_length = offset;

	Format* old_format;
	if (format->fmt_version &&
		(old_format = MET_format(tdbb, relation, (format->fmt_version - 1))) &&
		(formatsAreEqual(old_format, format)))
	{
		delete format;
		*version = old_format->fmt_version;
		return old_format;
	}

	// Link the format block into the world

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

	// Store format in system relation

	AutoCacheRequest request(tdbb, irq_format3, IRQ_REQUESTS);

	STORE(REQUEST_HANDLE request)
		FMTS IN RDB$FORMATS
	{
		FMTS.RDB$FORMAT = format->fmt_version;
		FMTS.RDB$RELATION_ID = relation->rel_id;
		blb* blob = blb::create(tdbb, sysTransaction, &FMTS.RDB$DESCRIPTOR);

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

		Firebird::Array<Ods::Descriptor> odsDescs;
		Ods::Descriptor* odsDesc = odsDescs.getBuffer(format->fmt_count);

		for (Format::fmt_desc_const_iterator desc = format->fmt_desc.begin();
			 desc < format->fmt_desc.end(); ++desc, ++odsDesc)
		{
			*odsDesc = *desc;
		}

		HalfStaticArray<UCHAR, BUFFER_MEDIUM> buffer;

		buffer.add(UCHAR(format->fmt_count));
		buffer.add(UCHAR(format->fmt_count >> 8));

		buffer.add((UCHAR*) odsDescs.begin(), odsDescs.getCount() * sizeof(Ods::Descriptor));

		const FB_SIZE_T pos = buffer.getCount();
		buffer.add(0);
		buffer.add(0);

		USHORT i = 0, dflCount = 0;
		for (Format::fmt_defaults_iterator impure = format->fmt_defaults.begin();
			 impure != format->fmt_defaults.end(); ++impure, ++i)
		{
			if (!impure->vlu_desc.isUnknown())
			{
				dsc desc = impure->vlu_desc;
				desc.dsc_address = NULL;

				Ods::Descriptor odsDflDesc = desc;

				buffer.add(UCHAR(i));
				buffer.add(UCHAR(i >> 8));
				buffer.add((UCHAR*) &odsDflDesc, sizeof(odsDflDesc));
				buffer.add(impure->vlu_desc.dsc_address, impure->vlu_desc.dsc_length);

				++dflCount;
			}
		}

		buffer[pos] = UCHAR(dflCount);
		buffer[pos + 1] = UCHAR(dflCount >> 8);

		blob->BLB_put_data(tdbb, buffer.begin(), buffer.getCount());
		blob->BLB_close(tdbb);
	}
	END_STORE

	return format;
}


static bool make_version(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	m a k e _ v e r s i o n
 *
 **************************************
 *
 * Functional description
 *	Make a new format version for a relation.  While we're at it, make
 *	sure all fields have id's.  If the relation is a view, make a
 *	a format anyway -- used for view updates.
 *
 *	While we're in the vicinity, also check the updatability of fields.
 *
 **************************************/
	TemporaryField* stack;
	TemporaryField* external;
	jrd_rel* relation;
	//bid blob_id;
	//blob_id.clear();

	USHORT n;
	int physical_fields = 0;
	bool external_flag = false;
	bool computed_field;
	TrigVector* triggers[TRIGGER_MAX];

	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();
	Database* dbb = tdbb->getDatabase();
	bool null_view;

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		relation = NULL;
		stack = external = NULL;
		computed_field = false;

		for (n = 0; n < TRIGGER_MAX; n++) {
			triggers[n] = NULL;
		}

		AutoCacheRequest request_fmt1(tdbb, irq_format1, IRQ_REQUESTS);

		// User transaction may be safely used instead of system cause
		// all required dirty reads are performed in metadata cache. AP-2008.

		FOR(REQUEST_HANDLE request_fmt1 TRANSACTION_HANDLE transaction)
			REL IN RDB$RELATIONS WITH REL.RDB$RELATION_NAME EQ work->dfw_name.c_str()
		{
			relation = MET_lookup_relation_id(tdbb, REL.RDB$RELATION_ID, false);
			if (!relation)
				return false;

			const bid blob_id = REL.RDB$VIEW_BLR;
			null_view = blob_id.isEmpty();
			external_flag = REL.RDB$EXTERNAL_FILE[0];

			if (REL.RDB$VIEW_BLR.NULL)
			{
				if (REL.RDB$FORMAT == MAX_TABLE_VERSIONS)
					raiseTooManyVersionsError(obj_relation, work->dfw_name);
			}
			else
			{
				if (REL.RDB$FORMAT == MAX_VIEW_VERSIONS)
					raiseTooManyVersionsError(obj_view, work->dfw_name);
			}

			MODIFY REL USING
				blb* blob = blb::create(tdbb, transaction, &REL.RDB$RUNTIME);
				AutoCacheRequest request_fmtx(tdbb, irq_format2, IRQ_REQUESTS);

				FOR(REQUEST_HANDLE request_fmtx TRANSACTION_HANDLE transaction)
					RFR IN RDB$RELATION_FIELDS CROSS
						FLD IN RDB$FIELDS WITH
						RFR.RDB$RELATION_NAME EQ work->dfw_name.c_str() AND
						RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME
						SORTED BY RFR.RDB$FIELD_POSITION
				{
					// Update RFR to reflect new fields id

					if (!RFR.RDB$FIELD_ID.NULL && RFR.RDB$FIELD_ID >= REL.RDB$FIELD_ID)
						REL.RDB$FIELD_ID = RFR.RDB$FIELD_ID + 1;

					// force recalculation of RDB$UPDATE_FLAG if field is calculated
					if (!FLD.RDB$COMPUTED_BLR.isEmpty())
					{
						RFR.RDB$UPDATE_FLAG.NULL = TRUE;
						computed_field = true;
					}

					if (RFR.RDB$FIELD_ID.NULL || RFR.RDB$UPDATE_FLAG.NULL)
					{
						MODIFY RFR USING
							if (RFR.RDB$FIELD_ID.NULL)
							{
								if (external_flag)
								{
									RFR.RDB$FIELD_ID = RFR.RDB$FIELD_POSITION;
									// RFR.RDB$FIELD_POSITION.NULL is
									// needed to be referenced in the
									// code somewhere for GPRE to include
									// this field in the structures that
									// it generates at the top of this func.
									RFR.RDB$FIELD_ID.NULL = RFR.RDB$FIELD_POSITION.NULL;
								}
								else
								{
									RFR.RDB$FIELD_ID = REL.RDB$FIELD_ID;
									RFR.RDB$FIELD_ID.NULL = FALSE;

									// If the table is being altered, check validity of NOT NULL fields.
									if (!REL.RDB$FORMAT.NULL)
									{
										bool notNull = (RFR.RDB$NULL_FLAG.NULL ?
											(FLD.RDB$NULL_FLAG.NULL ? false : (bool) FLD.RDB$NULL_FLAG) :
											(bool) RFR.RDB$NULL_FLAG);

										if (notNull)
										{
											dsc desc;
											desc.makeText(static_cast<USHORT>(strlen(REL.RDB$RELATION_NAME)),
												CS_METADATA, (UCHAR*) REL.RDB$RELATION_NAME);

											DeferredWork* work = DFW_post_work(transaction,
												dfw_check_not_null, &desc, 0);
											SortedArray<int>& ids = DFW_get_ids(work);

											FB_SIZE_T pos;
											if (!ids.find(RFR.RDB$FIELD_ID, pos))
												ids.insert(pos, RFR.RDB$FIELD_ID);
										}
									}
								}

								REL.RDB$FIELD_ID++;
							}
							if (RFR.RDB$UPDATE_FLAG.NULL)
							{
								RFR.RDB$UPDATE_FLAG.NULL = FALSE;
								RFR.RDB$UPDATE_FLAG = 1;
								if (!FLD.RDB$COMPUTED_BLR.isEmpty())
								{
									RFR.RDB$UPDATE_FLAG = 0;
								}
								if (!null_view && REL.RDB$DBKEY_LENGTH > 8)
								{
									AutoRequest temp;
									RFR.RDB$UPDATE_FLAG = 0;
									FOR(REQUEST_HANDLE temp) X IN RDB$TRIGGERS WITH
										X.RDB$RELATION_NAME EQ work->dfw_name.c_str() AND
											X.RDB$TRIGGER_TYPE EQ 1
									{
										RFR.RDB$UPDATE_FLAG = 1;
									}
									END_FOR
								}
							}
						END_MODIFY
					}

					// Store stuff in field summary

					n = RFR.RDB$FIELD_ID;
					put_summary_record(tdbb, blob, RSR_field_id, (UCHAR*)&n, sizeof(n));
					put_summary_record(tdbb, blob, RSR_field_name, (UCHAR*) RFR.RDB$FIELD_NAME,
									   fb_utils::name_length(RFR.RDB$FIELD_NAME));
					if (!FLD.RDB$COMPUTED_BLR.isEmpty() && !RFR.RDB$VIEW_CONTEXT)
					{
						put_summary_blob(tdbb, blob, RSR_computed_blr, &FLD.RDB$COMPUTED_BLR, transaction);
					}
					else if (!null_view)
					{
						n = RFR.RDB$VIEW_CONTEXT;
						put_summary_record(tdbb, blob, RSR_view_context, (UCHAR*)&n, sizeof(n));
						put_summary_record(tdbb, blob, RSR_base_field, (UCHAR*) RFR.RDB$BASE_FIELD,
										   fb_utils::name_length(RFR.RDB$BASE_FIELD));
					}
					put_summary_blob(tdbb, blob, RSR_missing_value, &FLD.RDB$MISSING_VALUE, transaction);

					bid* defaultValue = RFR.RDB$DEFAULT_VALUE.isEmpty() ?
						&FLD.RDB$DEFAULT_VALUE : &RFR.RDB$DEFAULT_VALUE;

					put_summary_blob(tdbb, blob, RSR_default_value, defaultValue, transaction);
					put_summary_blob(tdbb, blob, RSR_validation_blr, &FLD.RDB$VALIDATION_BLR, transaction);

					bool notNull = (RFR.RDB$NULL_FLAG.NULL ?
						(FLD.RDB$NULL_FLAG.NULL ? false : (bool) FLD.RDB$NULL_FLAG) :
						(bool) RFR.RDB$NULL_FLAG);

					if (notNull)
					{
						put_summary_record(tdbb, blob, RSR_field_not_null,
											nonnull_validation_blr, sizeof(nonnull_validation_blr));
					}

					n = fb_utils::name_length(RFR.RDB$SECURITY_CLASS);
					if (!RFR.RDB$SECURITY_CLASS.NULL && n)
					{
						put_summary_record(tdbb, blob, RSR_security_class,
										   (UCHAR*) RFR.RDB$SECURITY_CLASS, n);
					}

					n = fb_utils::name_length(RFR.RDB$GENERATOR_NAME);
					if (!RFR.RDB$GENERATOR_NAME.NULL && n)
					{
						put_summary_record(tdbb, blob, RSR_field_generator_name,
										   (UCHAR*) RFR.RDB$GENERATOR_NAME, n);
					}

					n = RFR.RDB$IDENTITY_TYPE;
					if (!RFR.RDB$IDENTITY_TYPE.NULL)
						put_summary_record(tdbb, blob, RSR_field_identity_type, (UCHAR*) &n, sizeof(n));

					// Make a temporary field block

					TemporaryField* tfb = FB_NEW_POOL(*tdbb->getDefaultPool()) TemporaryField;
					tfb->tfb_next = stack;
					stack = tfb;

					// for text data types, grab the CHARACTER_SET and
					// COLLATION to give the type of international text

					if (FLD.RDB$CHARACTER_SET_ID.NULL)
						FLD.RDB$CHARACTER_SET_ID = CS_NONE;

					SSHORT collation = COLLATE_NONE;	// codepoint collation
					if (!FLD.RDB$COLLATION_ID.NULL)
						collation = FLD.RDB$COLLATION_ID;
					if (!RFR.RDB$COLLATION_ID.NULL)
						collation = RFR.RDB$COLLATION_ID;

					if (!DSC_make_descriptor(&tfb->tfb_desc, FLD.RDB$FIELD_TYPE,
											 FLD.RDB$FIELD_SCALE,
											 FLD.RDB$FIELD_LENGTH,
											 FLD.RDB$FIELD_SUB_TYPE,
											 FLD.RDB$CHARACTER_SET_ID, collation))
					{
						if (null_view && REL.RDB$FORMAT.NULL)
							DPM_delete_relation(tdbb, relation);

						ERR_post(Arg::Gds(isc_no_meta_update) <<
								 Arg::Gds(isc_random) << Arg::Str(work->dfw_name));
					}

					// Make sure the text type specified is implemented
					if (!validate_text_type(tdbb, tfb))
					{
						if (null_view && REL.RDB$FORMAT.NULL)
							DPM_delete_relation(tdbb, relation);

						ERR_post_nothrow(Arg::Gds(isc_no_meta_update) <<
										 Arg::Gds(isc_random) << Arg::Str(work->dfw_name));
						INTL_texttype_lookup(tdbb,
							(DTYPE_IS_TEXT(tfb->tfb_desc.dsc_dtype) ?
								tfb->tfb_desc.dsc_ttype() : tfb->tfb_desc.dsc_blob_ttype()));	// should punt
						ERR_punt(); // if INTL_texttype_lookup hasn't punt
					}

					// Store the default value.

					memset(&tfb->tfb_default, 0, sizeof(tfb->tfb_default));

					if (notNull && !defaultValue->isEmpty())
					{
						Jrd::ContextPoolHolder context(tdbb, attachment->createPool());
						JrdStatement* defaultStatement = NULL;
						try
						{
							ValueExprNode* defaultNode = static_cast<ValueExprNode*>(MET_parse_blob(
								tdbb, relation, defaultValue, NULL, &defaultStatement, false, false));

							jrd_req* const defaultRequest = defaultStatement->findRequest(tdbb);

							// Attention: this is scoped to the end of this "try".
							AutoSetRestore2<jrd_req*, thread_db> autoRequest(tdbb,
								&thread_db::getRequest, &thread_db::setRequest, defaultRequest);

							defaultRequest->validateTimeStamp();

							dsc* result = nullptr;
							{ // scope
								Firebird::Cleanup detach([&defaultRequest] {TRA_detach_request(defaultRequest);});
								TRA_attach_request(transaction, defaultRequest);
								result = EVL_expr(tdbb, defaultRequest, defaultNode);
							}

							if (result)
							{
								dsc desc = *result;
								MoveBuffer buffer;

								if (desc.isText() || desc.isBlob())
								{
									UCHAR* ptr = NULL;
									const int len = MOV_make_string2(tdbb, &desc, tfb->tfb_desc.getCharSet(),
										&ptr, buffer, true);
									fb_assert(ULONG(len) < ULONG(MAX_USHORT));
									desc.makeText(len, tfb->tfb_desc.getCharSet(), ptr);
								}

								impure_value tempValue;
								MoveBuffer tempBuffer;

								if (!tfb->tfb_desc.isBlob() && !DSC_EQUIV(result, &tfb->tfb_desc, false))
								{
									tempValue.vlu_desc = tfb->tfb_desc;

									if (tfb->tfb_desc.isText())
										tempValue.vlu_desc.dsc_address = tempBuffer.getBuffer(tfb->tfb_desc.dsc_length);
									else
										tempValue.vlu_desc.dsc_address = (UCHAR*) &tempValue.vlu_misc;

									try
									{
										MOV_move(tdbb, &desc, &tempValue.vlu_desc);
										desc = tempValue.vlu_desc;
									}
									catch (const status_exception&)
									{
										if (!tdbb->getAttachment()->isGbak())
											throw;

										// If we're restoring a database, ignore the error and use the original desc.
										fb_utils::init_status(tdbb->tdbb_status_vector);
									}
								}

								EVL_make_value(tdbb, &desc, &tfb->tfb_default, relation->rel_pool);
							}
						}
						catch (const Exception&)
						{
							if (defaultStatement)
								defaultStatement->release(tdbb);
							throw;
						}

						defaultStatement->release(tdbb);
					}

					// dimitr: view fields shouldn't be marked as computed
					if (null_view && !FLD.RDB$COMPUTED_BLR.isEmpty())
						tfb->tfb_flags |= TFB_computed;
					else
						++physical_fields;

					tfb->tfb_id = RFR.RDB$FIELD_ID;

					if ((n = FLD.RDB$DIMENSIONS))
						setup_array(tdbb, blob, FLD.RDB$FIELD_NAME, n, tfb);

					if (external_flag)
					{
						tfb = FB_NEW_POOL(*tdbb->getDefaultPool()) TemporaryField;
						tfb->tfb_next = external;
						external = tfb;
						fb_assert(FLD.RDB$EXTERNAL_TYPE <= MAX_UCHAR);
						tfb->tfb_desc.dsc_dtype = (UCHAR)FLD.RDB$EXTERNAL_TYPE;
						fb_assert(FLD.RDB$EXTERNAL_SCALE >= MIN_SCHAR &&
							FLD.RDB$EXTERNAL_SCALE <= MAX_SCHAR);
						tfb->tfb_desc.dsc_scale = (SCHAR)FLD.RDB$EXTERNAL_SCALE;
						tfb->tfb_desc.dsc_length = FLD.RDB$EXTERNAL_LENGTH;
						tfb->tfb_id = RFR.RDB$FIELD_ID;
					}
				}
				END_FOR

				if (null_view && !physical_fields)
				{
					if (REL.RDB$FORMAT.NULL)
						DPM_delete_relation(tdbb, relation);

					ERR_post(Arg::Gds(isc_no_meta_update) <<
							 Arg::Gds(isc_table_name) << Arg::Str(work->dfw_name) <<
			 				 Arg::Gds(isc_must_have_phys_field));
				}

				blob = setup_triggers(tdbb, relation, null_view, triggers, blob);

				blob->BLB_close(tdbb);
				USHORT version = REL.RDB$FORMAT.NULL ? 0 : REL.RDB$FORMAT;
				version++;
				relation->rel_current_format = make_format(tdbb, relation, &version, stack);
				REL.RDB$FORMAT.NULL = FALSE;
				REL.RDB$FORMAT = version;

				if (!null_view)
				{
					// update the dbkey length to include each of the base relations

					REL.RDB$DBKEY_LENGTH = 0;

					AutoRequest handle;
					FOR(REQUEST_HANDLE handle)
						Z IN RDB$VIEW_RELATIONS
						CROSS R IN RDB$RELATIONS OVER RDB$RELATION_NAME
						WITH Z.RDB$VIEW_NAME = work->dfw_name.c_str()
					{
						REL.RDB$DBKEY_LENGTH += R.RDB$DBKEY_LENGTH;
					}
					END_FOR
				}
			END_MODIFY
		}
		END_FOR

		// If we didn't find the relation, it is probably being dropped

		if (!relation)
			return false;

		// We have just loaded the triggers onto the local vector triggers.
		// It's now time to place them at their rightful place inside the relation block.

		if (!(relation->rel_flags & REL_sys_trigs_being_loaded))
			relation->replaceTriggers(tdbb, triggers);

		// in case somebody changed the view definition or a computed
		// field, reset the dependencies by deleting the current ones
		// and setting a flag for MET_scan_relation to find the new ones

		if (!null_view)
			MET_delete_dependencies(tdbb, work->dfw_name, obj_view, transaction);

		{ // begin scope
			const DeferredWork* arg = work->findArg(dfw_arg_force_computed);
			if (arg)
			{
				computed_field = true;
				MET_delete_dependencies(tdbb, arg->dfw_name, obj_computed, transaction);
			}
		} // end scope

		if (!null_view || computed_field)
			relation->rel_flags |= REL_get_dependencies;

		if (external_flag)
		{
			AutoRequest temp;
			FOR(REQUEST_HANDLE temp) FMTS IN RDB$FORMATS WITH
				FMTS.RDB$RELATION_ID EQ relation->rel_id AND
				FMTS.RDB$FORMAT EQ 0
			{
				ERASE FMTS;
			}
			END_FOR

			make_format(tdbb, relation, 0, external);
		}

		relation->rel_flags &= ~REL_scanned;
		DFW_post_work(transaction, dfw_scan_relation, NULL, relation->rel_id);

		// signal others about new format presence
		LCK_release(tdbb, relation->rel_rescan_lock);
		{
			// Use temp lock to avoid AST call
			Lock tmpLock(tdbb, sizeof(SLONG), LCK_rel_rescan);
			tmpLock.setKey(relation->rel_id);

			LCK_lock(tdbb, &tmpLock, LCK_EX, LCK_WAIT);
			LCK_release(tdbb, &tmpLock);
		}

		break;
	}

	return false;
}


static bool modify_trigger(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra* transaction)
{
/**************************************
 *
 *	m o d i f y _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *	Perform required actions when modifying trigger.
 *
 **************************************/

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

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		{
			bool compile = !work->findArg(dfw_arg_check_blr);

			// get rid of old dependencies, bring in the new
			MET_delete_dependencies(tdbb, work->dfw_name, obj_trigger, transaction);
			get_trigger_dependencies(work, compile, transaction);
		}
		return true;

	case 4:
		{
			const DeferredWork* arg = work->findArg(dfw_arg_rel_name);
			if (!arg)
			{
				arg = work->findArg(dfw_arg_trg_type);
				fb_assert(arg);

				// ASF: arg->dfw_id is RDB$TRIGGER_TYPE truncated to USHORT
				if (arg && (arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DB)
				{
					unsigned triggerKind = arg->dfw_id & ~TRIGGER_TYPE_DB;
					MET_release_triggers(tdbb, &tdbb->getAttachment()->att_triggers[triggerKind], true);
					MET_load_db_triggers(tdbb, triggerKind);
				}
				else if ((arg->dfw_id & TRIGGER_TYPE_MASK) == TRIGGER_TYPE_DDL)
				{
					MET_release_triggers(tdbb, &tdbb->getAttachment()->att_ddl_triggers, true);
					MET_load_ddl_triggers(tdbb);
				}
			}
		}

		{ // scope
			const DeferredWork* arg = work->findArg(dfw_arg_check_blr);
			if (arg)
			{
				const MetaName relation_name(arg->dfw_name);
				SSHORT valid_blr = FALSE;

				try
				{
					jrd_rel* relation = MET_lookup_relation(tdbb, relation_name);

					if (relation)
					{
						// remove cached triggers from relation
						relation->rel_flags &= ~REL_scanned;
						MET_scan_relation(tdbb, relation);

						TrigVector* triggers[TRIGGER_MAX];

						for (int i = 0; i < TRIGGER_MAX; ++i)
							triggers[i] = NULL;

						MemoryPool* new_pool = attachment->createPool();
						try
						{
							Jrd::ContextPoolHolder context(tdbb, new_pool);

							MET_load_trigger(tdbb, relation, work->dfw_name, triggers);

							for (int i = 0; i < TRIGGER_MAX; ++i)
							{
								if (triggers[i])
								{
									for (FB_SIZE_T j = 0; j < triggers[i]->getCount(); ++j)
										(*triggers[i])[j].compile(tdbb);

									MET_release_triggers(tdbb, &triggers[i], true);
								}
							}

							valid_blr = TRUE;
						}
						catch (const Firebird::Exception&)
						{
							attachment->deletePool(new_pool);
							throw;
						}

						attachment->deletePool(new_pool);
					}
				}
				catch (const Firebird::Exception&)
				{
				}

				AutoCacheRequest request(tdbb, irq_trg_validate, IRQ_REQUESTS);

				FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
					TRG IN RDB$TRIGGERS WITH
						TRG.RDB$TRIGGER_NAME EQ work->dfw_name.c_str() AND TRG.RDB$TRIGGER_BLR NOT MISSING
				{
					MODIFY TRG USING
						TRG.RDB$VALID_BLR = valid_blr;
						TRG.RDB$VALID_BLR.NULL = FALSE;
					END_MODIFY
				}
				END_FOR
			}
		} // scope
		break;
	}

	return false;
}


static void put_summary_blob(thread_db* tdbb, blb* blob, rsr_t type, bid* blob_id, jrd_tra* transaction)
{
/**************************************
 *
 *	p u t _ s u m m a r y _ b l o b
 *
 **************************************
 *
 * Functional description
 *	Put an attribute record to the relation summary blob.
 *
 **************************************/

	SET_TDBB(tdbb);

	if (blob_id->isEmpty())	// If blob is null, don't bother.
		return;

	// Go ahead and open blob
	blb* blr = blb::open(tdbb, transaction, blob_id);

	ULONG length = blr->blb_length;
	// We cannot deal with chunks longer than a max blob segment (minus one)
	fb_assert(length < MAX_USHORT);

	HalfStaticArray<UCHAR, BUFFER_TINY> buffer;
	UCHAR* p = buffer.getBuffer(length + 1);
	*p++ = (UCHAR) type;

	length = blr->BLB_get_data(tdbb, p, length);

	blob->BLB_put_segment(tdbb, buffer.begin(), length + 1);
}


static void put_summary_record(thread_db* tdbb,
							   blb*		blob,
							   rsr_t	type,
							   const UCHAR*	data,
							   ULONG	length)
{
/**************************************
 *
 *	p u t _ s u m m a r y _ r e c o r d
 *
 **************************************
 *
 * Functional description
 *	Put an attribute record to the relation summary blob.
 *
 **************************************/
	// We cannot deal with chunks longer than a max blob segment (minus one)
	fb_assert(length < MAX_USHORT);

	SET_TDBB(tdbb);

	HalfStaticArray<UCHAR, BUFFER_TINY> buffer;
	UCHAR* p = buffer.getBuffer(length + 1);
	*p++ = (UCHAR) type;
	memcpy(p, data, length);

	blob->BLB_put_segment(tdbb, buffer.begin(), length + 1);
}


static bool scan_relation(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	s c a n _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *	Call MET_scan_relation with the appropriate
 *	relation.
 *
 **************************************/

	SET_TDBB(tdbb);

	switch (phase)
	{
	case 1:
	case 2:
		return true;

	case 3:
		// dimitr:	I suspect that nobody expects an updated format to
		//			appear at stage 3, so the logic would work reliably
		//			if this line is removed (and hence we rely on the
		//			4th stage only). But I leave it here for the time being.
		MET_scan_relation(tdbb, MET_relation(tdbb, work->dfw_id));
		return true;

	case 4:
		MET_scan_relation(tdbb, MET_relation(tdbb, work->dfw_id));
		break;
	}

	return false;
}


static bool change_repl_state(thread_db* tdbb, SSHORT phase, DeferredWork* work, jrd_tra*)
{
/**************************************
 *
 *	c h a n g e _ r e p l _ s t a t e
 *
 **************************************
 *
 * Functional description
 *	Signal other processes to refresh their replication state.
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();
	Attachment* const attachment = tdbb->getAttachment();

	switch (phase)
	{
	case 1:
	case 2:
	case 3:
		return true;

	case 4:
		if (work->dfw_id == 0)
		{
			// replication state is changed
			dbb->invalidateReplState(tdbb, true);
		}
		else
		{
			// replication set is changed
			attachment->invalidateReplSet(tdbb, true);
		}
		break;
	}

	return false;
}


#ifdef NOT_USED_OR_REPLACED
static bool shadow_defined(thread_db* tdbb)
{
/**************************************
 *
 *	s h a d o w _ d e f i n e d
 *
 **************************************
 *
 * Functional description
 *	Return true if any shadows have been has been defined
 *      for the database else return false.
 *
 **************************************/

	SET_TDBB(tdbb);
	Database* dbb = tdbb->getDatabase();

	bool result = false;
	AutoRequest handle;

	FOR(REQUEST_HANDLE handle) FIRST 1 X IN RDB$FILES
		WITH X.RDB$SHADOW_NUMBER > 0
	{
		result = true;
	}
	END_FOR

	return result;
}
#endif


static void setup_array(thread_db* tdbb, blb* blob, const TEXT* field_name, USHORT n,
	TemporaryField* tfb)
{
/**************************************
 *
 *	s e t u p _ a r r a y
 *
 **************************************
 *
 * Functional description
 *
 *	setup an array descriptor in a tfb
 *
 **************************************/

	SLONG stuff[256];

	put_summary_record(tdbb, blob, RSR_dimensions, (UCHAR*) &n, sizeof(n));
	tfb->tfb_flags |= TFB_array;
	Ods::InternalArrayDesc* array = reinterpret_cast<Ods::InternalArrayDesc*>(stuff);
	MOVE_CLEAR(array, (SLONG) sizeof(Ods::InternalArrayDesc));
	array->iad_dimensions = n;
	array->iad_struct_count = 1;
	array->iad_rpt[0].iad_desc = tfb->tfb_desc;
	get_array_desc(tdbb, field_name, array);
	put_summary_record(tdbb, blob, RSR_array_desc, (UCHAR*) array, array->iad_length);
}


static blb* setup_triggers(thread_db* tdbb, jrd_rel* relation, bool null_view,
	TrigVector** triggers, blb* blob)
{
/**************************************
 *
 *	s e t u p _ t r i g g e r s
 *
 **************************************
 *
 * Functional description
 *
 *	Get the triggers in the right order, which appears
 *      to be system triggers first, then user triggers,
 *      then triggers that implement check constraints.
 *
 * BUG #8458: Check constraint triggers have to be loaded
 *	      (and hence executed) after the user-defined
 *	      triggers because user-defined triggers can modify
 *	      the values being inserted or updated so that
 *	      the end values stored in the database don't
 *	      fulfill the check constraint.
 *
 **************************************/
	if (!relation)
		return blob;

	Jrd::Attachment* attachment = tdbb->getAttachment();

	// system triggers

	AutoCacheRequest request_fmtx(tdbb, irq_format4, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request_fmtx)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$RELATION_NAME = relation->rel_name.c_str()
			AND TRG.RDB$SYSTEM_FLAG = 1
		SORTED BY TRG.RDB$TRIGGER_SEQUENCE
	{
		if (!TRG.RDB$TRIGGER_INACTIVE)
			setup_trigger_details(tdbb, relation, blob, triggers, TRG.RDB$TRIGGER_NAME, null_view);
	}
	END_FOR

	// user triggers

	request_fmtx.reset(tdbb, irq_format5, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request_fmtx)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$RELATION_NAME EQ relation->rel_name.c_str()
		AND TRG.RDB$SYSTEM_FLAG = 0
		AND (NOT ANY
			CHK IN RDB$CHECK_CONSTRAINTS CROSS
			RCN IN RDB$RELATION_CONSTRAINTS
			WITH TRG.RDB$TRIGGER_NAME EQ CHK.RDB$TRIGGER_NAME
			AND CHK.RDB$CONSTRAINT_NAME EQ RCN.RDB$CONSTRAINT_NAME
			AND (RCN.RDB$CONSTRAINT_TYPE EQ CHECK_CNSTRT
			    OR RCN.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY)
			)
		SORTED BY TRG.RDB$TRIGGER_SEQUENCE
	{
		if (!TRG.RDB$TRIGGER_INACTIVE)
			setup_trigger_details(tdbb, relation, blob, triggers, TRG.RDB$TRIGGER_NAME, null_view);
	}
	END_FOR

	// check constraint triggers.  We're looking for triggers that belong
	// to the table and are system triggers (i.e. system flag in (3, 4, 5))
	// or a user looking trigger that's involved in a check constraint

	request_fmtx.reset(tdbb, irq_format6, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE request_fmtx)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$RELATION_NAME = relation->rel_name.c_str()
		AND (TRG.RDB$SYSTEM_FLAG BT fb_sysflag_check_constraint AND fb_sysflag_view_check
			OR (TRG.RDB$SYSTEM_FLAG = 0 AND ANY
			CHK IN RDB$CHECK_CONSTRAINTS CROSS
			RCN IN RDB$RELATION_CONSTRAINTS
				WITH TRG.RDB$TRIGGER_NAME EQ CHK.RDB$TRIGGER_NAME
				AND CHK.RDB$CONSTRAINT_NAME EQ RCN.RDB$CONSTRAINT_NAME
				AND (RCN.RDB$CONSTRAINT_TYPE EQ CHECK_CNSTRT
				    OR RCN.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY)
		    	)
			)
		SORTED BY TRG.RDB$TRIGGER_SEQUENCE
	{
		if (!TRG.RDB$TRIGGER_INACTIVE)
			setup_trigger_details(tdbb, relation, blob, triggers, TRG.RDB$TRIGGER_NAME, null_view);
	}
	END_FOR

	return blob;
}


static void setup_trigger_details(thread_db* tdbb,
								  jrd_rel* relation,
								  blb* blob,
								  TrigVector** triggers,
								  const TEXT* trigger_name,
								  bool null_view)
{
/**************************************
 *
 *	s e t u p _ t r i g g e r _ d e t a i l s
 *
 **************************************
 *
 * Functional description
 *	Stuff trigger details in places.
 *
 * for a view, load the trigger temporarily --
 * this is inefficient since it will just be reloaded
 *  in MET_scan_relation () but it needs to be done
 *  in case the view would otherwise be non-updatable
 *
 **************************************/

	put_summary_record(tdbb, blob, RSR_trigger_name,
		(const UCHAR*) trigger_name, fb_utils::name_length(trigger_name));

	if (!null_view) {
		MET_load_trigger(tdbb, relation, trigger_name, triggers);
	}
}


static bool validate_text_type(thread_db* tdbb, const TemporaryField* tfb)
{
/**************************************
 *
 *	v a l i d a t e _ t e x t _ t y p e
 *
 **************************************
 *
 * Functional description
 *	Make sure the text type specified is implemented
 *
 **************************************/

	if ((DTYPE_IS_TEXT (tfb->tfb_desc.dsc_dtype) &&
		!INTL_defined_type(tdbb, tfb->tfb_desc.dsc_ttype())) ||
		(tfb->tfb_desc.dsc_dtype == dtype_blob && tfb->tfb_desc.dsc_sub_type == isc_blob_text &&
			!INTL_defined_type(tdbb, tfb->tfb_desc.dsc_blob_ttype())))
	{
		return false;
	}

	return true;
}


static string get_string(const dsc* desc)
{
/**************************************
 *
 *  g e t _ s t r i n g
 *
 **************************************
 *
 * Get string for a given descriptor.
 *
 **************************************/
	const char* str;
	VaryStr<MAXPATHLEN> temp;// Must hold largest metadata field or filename

	if (!desc)
	{
		return string();
	}

	// Find the actual length of the string, searching until the claimed
	// end of the string, or the terminating \0, whichever comes first.

	USHORT length = MOV_make_string(JRD_get_thread_data(), desc, ttype_metadata, &str, &temp, sizeof(temp));

	const char* p = str;
	const char* const q = str + length;
	while (p < q && *p)
	{
		++p;
	}

	// Trim trailing blanks (bug 3355)

	while (--p >= str && *p == ' ')
		;
	length = (p + 1) - str;

	return string(str, length);
}
