/*
 *  PROGRAM:    Dynamic SQL runtime support
 *  MODULE:     metd.epp
 *  DESCRIPTION:    Meta-data interface
 *
 * 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.11.28 Claudio Valderrama: load not only udfs but udf arguments;
 *   handle possible collisions with udf redefinitions (drop->declare).
 *   This closes SF Bug# 409769.
 * 2001.12.06 Claudio Valderrama: METD_get_charset_bpc() was added to
 *    get only the bytes per char of a field, given its charset id.
 *   This request is not cached.
 * 2001.02.23 Claudio Valderrama: Fix SF Bug #228135 with views spoiling
 *    NULLs in outer joins.
 * 2004.01.16 Vlad Horsun: make METD_get_col_default and
 *   METD_get_domain_default return actual length of default BLR
 * 2004.01.16 Vlad Horsun: added support for default parameters
 */

#include "firebird.h"
#include <string.h>
#include "../dsql/dsql.h"
#include "../jrd/ibase.h"
#include "../jrd/align.h"
#include "../jrd/intl.h"
#include "../jrd/irq.h"
#include "../jrd/tra.h"
#include "../dsql/ddl_proto.h"
#include "../dsql/metd_proto.h"
#include "../dsql/hsh_proto.h"
#include "../dsql/make_proto.h"
#include "../dsql/errd_proto.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/gds_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/thread_proto.h"
#include "../jrd/why_proto.h"
#include "../common/utils_proto.h"
#include "../common/classes/init.h"

using namespace Jrd;
using namespace Firebird;

// NOTE: The static definition of DB and gds_trans by gpre will not
// be used by the meta data routines.  Each of those routines has
// its own local definition of these variables.

DATABASE DB = STATIC "yachts.lnk";

static const UCHAR blr_bpb[] =
{
	isc_bpb_version1,
	isc_bpb_source_type, 1, isc_blob_blr,
	isc_bpb_target_type, 1, isc_blob_blr
};

static void convert_dtype(dsql_fld*, SSHORT);
static void free_procedure(dsql_prc*);
static void free_relation(dsql_rel*);
static void insert_symbol(dsql_sym*);
static dsql_sym* lookup_symbol(dsql_dbb*, USHORT, const char*, const SYM_TYPE, USHORT = 0);
static dsql_sym* lookup_symbol(dsql_dbb*, const dsql_str*, SYM_TYPE, USHORT = 0);

#define DSQL_REQUEST(id) dbb->dbb_database->dbb_internal[id]

namespace {
	class MutexHolder : public Database::CheckoutLockGuard
	{
	public:
		explicit MutexHolder(dsql_req* request)
			: CheckoutLockGuard(request->req_dbb->dbb_database, request->req_dbb->dbb_cache_mutex)
		{}

	private:
		// copying is prohibited
		MutexHolder(const MutexHolder&);
		MutexHolder& operator=(const MutexHolder&);
	};

	inline void validateTransaction(const dsql_req* request)
	{
		if (!request->req_transaction->checkHandle())
		{
			ERR_post(Arg::Gds(isc_bad_trans_handle));
		}
	}
}


void METD_drop_charset(dsql_req* request, const MetaName& name)
{
/**************************************
 *
 *  M E T D _ d r o p _ c h a r s e t
 *
 **************************************
 *
 * Functional description
 *  Drop a character set from our metadata, and the next caller who wants it will
 *  look up the new version.
 *  Dropping will be achieved by marking the character set
 *  as dropped.  Anyone with current access can continue
 *  accessing it.
 *
 **************************************/
	MutexHolder holder(request);

	// If the symbol wasn't defined, we've got nothing to do
	dsql_sym* symbol = lookup_symbol(request->req_dbb, name.length(), name.c_str(),
									 SYM_intlsym_charset, 0);

	if (symbol)
	{
		dsql_intlsym* intlsym = (dsql_intlsym*) symbol->sym_object;
		intlsym->intlsym_flags |= INTLSYM_dropped;
	}

	// mark other potential candidates as maybe dropped
	HSHD_set_flag(request->req_dbb, name.c_str(), name.length(), SYM_intlsym_charset, INTLSYM_dropped);
}


void METD_drop_collation(dsql_req* request, const dsql_str* name)
{
/**************************************
 *
 *  M E T D _ d r o p _ c o l l a t i o n
 *
 **************************************
 *
 * Functional description
 *  Drop a collation from our metadata, and
 *  the next caller who wants it will
 *  look up the new version.
 *
 *  Dropping will be achieved by marking the collation
 *  as dropped.  Anyone with current access can continue
 *  accessing it.
 *
 **************************************/
	MutexHolder holder(request);

	// If the symbol wasn't defined, we've got nothing to do
	dsql_sym* symbol = lookup_symbol(request->req_dbb, name, SYM_intlsym_collation, 0);

	if (symbol)
	{
		dsql_intlsym* intlsym = (dsql_intlsym*) symbol->sym_object;
		intlsym->intlsym_flags |= INTLSYM_dropped;
	}

	// mark other potential candidates as maybe dropped
	HSHD_set_flag(request->req_dbb, name->str_data, name->str_length,
				  SYM_intlsym_collation, INTLSYM_dropped);
}


void METD_drop_function(dsql_req* request, const dsql_str* name)
{
/**************************************
 *
 *  M E T D _ d r o p _ f u n c t i o n
 *
 **************************************
 *
 * Functional description
 *  Drop a user defined function from our metadata, and
 *  the next caller who wants it will
 *  look up the new version.
 *
 *  Dropping will be achieved by marking the function
 *  as dropped.  Anyone with current access can continue
 *  accessing it.
 *
 **************************************/
	MutexHolder holder(request);

	// If the symbol wasn't defined, we've got nothing to do

	dsql_sym* symbol = lookup_symbol(request->req_dbb, name, SYM_udf);

	if (symbol) {
		dsql_udf* userFunc = (dsql_udf*) symbol->sym_object;
		userFunc->udf_flags |= UDF_dropped;
	}

	// mark other potential candidates as maybe dropped

	HSHD_set_flag(request->req_dbb, name->str_data, name->str_length, SYM_udf, UDF_dropped);

}


void METD_drop_procedure(dsql_req* request, const dsql_str* name)
{
/**************************************
 *
 *  M E T D _ d r o p _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *  Drop a procedure from our metadata, and
 *  the next caller who wants it will
 *  look up the new version.
 *
 *  Dropping will be achieved by marking the procedure
 *  as dropped.  Anyone with current access can continue
 *  accessing it.
 *
 **************************************/
	MutexHolder holder(request);

	// If the symbol wasn't defined, we've got nothing to do

	dsql_sym* symbol = lookup_symbol(request->req_dbb, name, SYM_procedure);

	if (symbol) {
		dsql_prc* procedure = (dsql_prc*) symbol->sym_object;
		procedure->prc_flags |= PRC_dropped;
	}

	// mark other potential candidates as maybe dropped

	HSHD_set_flag(request->req_dbb, name->str_data, name->str_length, SYM_procedure, PRC_dropped);

}


void METD_drop_relation(dsql_req* request, const dsql_str* name)
{
/**************************************
 *
 *  M E T D _ d r o p _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *  Drop a relation from our metadata, and
 *  rely on the next guy who wants it to
 *  look up the new version.
 *
 *      Dropping will be achieved by marking the relation
 *      as dropped.  Anyone with current access can continue
 *      accessing it.
 *
 **************************************/
	MutexHolder holder(request);

	// If the symbol wasn't defined, we've got nothing to do

	dsql_sym* symbol = lookup_symbol(request->req_dbb, name, SYM_relation);

	if (symbol) {
		dsql_rel* relation = (dsql_rel*) symbol->sym_object;
		relation->rel_flags |= REL_dropped;
	}

	// mark other potential candidates as maybe dropped

	HSHD_set_flag(request->req_dbb, name->str_data, name->str_length, SYM_relation, REL_dropped);

}


dsql_intlsym* METD_get_collation(dsql_req* request,
								 const dsql_str* name,
								 USHORT charset_id)
{
/**************************************
 *
 *  M E T D _ g e t _ c o l l a t i o n
 *
 **************************************
 *
 * Functional description
 *  Look up an international text type object.
 *  If it doesn't exist, return NULL.
 *
 **************************************/
	MutexHolder holder(request);

	thread_db* tdbb = JRD_get_thread_data();

	// Start by seeing if symbol is already defined

	dsql_sym* symbol = lookup_symbol(request->req_dbb, name, SYM_intlsym_collation, charset_id);
	if (symbol)
		return (dsql_intlsym*) symbol->sym_object;

	// Now see if it is in the database

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;
	dsql_intlsym* iname = NULL;

	jrd_req* handle = CMP_find_request(tdbb, irq_collation, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
	X IN RDB$COLLATIONS
		CROSS Y IN RDB$CHARACTER_SETS OVER RDB$CHARACTER_SET_ID
		WITH X.RDB$COLLATION_NAME EQ name->str_data AND
			 X.RDB$CHARACTER_SET_ID EQ charset_id;

		if (!DSQL_REQUEST(irq_collation))
			DSQL_REQUEST(irq_collation) = handle;

		iname = FB_NEW_RPT(dbb->dbb_pool, name->str_length) dsql_intlsym;
		strcpy(iname->intlsym_name, name->str_data);
		iname->intlsym_flags = 0;
		iname->intlsym_charset_id = X.RDB$CHARACTER_SET_ID;
		iname->intlsym_collate_id = X.RDB$COLLATION_ID;
		iname->intlsym_ttype =
			INTL_CS_COLL_TO_TTYPE(iname->intlsym_charset_id, iname->intlsym_collate_id);
		iname->intlsym_bytes_per_char =
			(Y.RDB$BYTES_PER_CHARACTER.NULL) ? 1 : (Y.RDB$BYTES_PER_CHARACTER);

	END_FOR

	if (!DSQL_REQUEST(irq_collation))
		DSQL_REQUEST(irq_collation) = handle;

	if (!iname)
		return NULL;

	// Store in the symbol table

	symbol = iname->intlsym_symbol = FB_NEW_RPT(dbb->dbb_pool, 0) dsql_sym;
	symbol->sym_object = iname;
	symbol->sym_string = iname->intlsym_name;
	symbol->sym_length = name->str_length;
	symbol->sym_type = SYM_intlsym_collation;
	symbol->sym_dbb = dbb;
	insert_symbol(symbol);

	return iname;
}


USHORT METD_get_col_default(dsql_req* request,
							const char* for_rel_name,
							const char* for_col_name,
							bool* has_default,
							UCHAR* buffer,
							USHORT buff_length)
{
/*************************************************************
 *
 *  M E T D _ g e t _ c o l _ d e f a u l t
 *
 **************************************************************
 *
 * Function:
 *    Gets the default value for a column of an existing table.
 *    Will check the default for the column of the table, if that is
 *    not present, will check for the default of the relevant domain
 *
 *    The default blr is returned in buffer. The blr is of the form
 *    blr_version4 blr_literal ..... blr_eoc
 *
 *    Reads the system tables RDB$FIELDS and RDB$RELATION_FIELDS.
 *
 **************************************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;

	bid* blob_id;

	USHORT result = 0;
	blb* blob_handle = 0;

	*has_default = false;

	jrd_req* handle = CMP_find_request(tdbb, irq_col_default, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		RFL IN RDB$RELATION_FIELDS CROSS
		FLD IN RDB$FIELDS WITH
		RFL.RDB$RELATION_NAME EQ for_rel_name AND
		RFL.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND
		RFL.RDB$FIELD_NAME EQ for_col_name

		if (!DSQL_REQUEST(irq_col_default))
			DSQL_REQUEST(irq_col_default) = handle;

		if (!RFL.RDB$DEFAULT_VALUE.NULL) {
			blob_id = &RFL.RDB$DEFAULT_VALUE;
			*has_default = true;
		}
		else if (!FLD.RDB$DEFAULT_VALUE.NULL) {
			blob_id = &FLD.RDB$DEFAULT_VALUE;
			*has_default = true;
		}
		else
			*has_default = false;

		if (*has_default)
		{
			blob_handle = BLB_open2(tdbb, request->req_transaction, blob_id,
									sizeof(blr_bpb), blr_bpb, true);

			// fetch segments. Assuming here that the buffer is big enough.
			UCHAR* ptr_in_buffer = buffer;
			while (true)
			{
				const USHORT length = BLB_get_segment(tdbb, blob_handle, ptr_in_buffer, buff_length);

				ptr_in_buffer += length;
				buff_length -= length;
				result += length;

				if (blob_handle->blb_flags & BLB_eof)
				{
					// null terminate the buffer
					*ptr_in_buffer = 0;
					break;
				}
				if (blob_handle->blb_fragment_size)
					status_exception::raise(Arg::Gds(isc_segment));
				else
					continue;
			}

			try
			{
				ThreadStatusGuard status_vector(tdbb);

				BLB_close(tdbb, blob_handle);
				blob_handle = NULL;
			}
			catch (Exception&)
			{
			}

			// the default string must be of the form:
			// blr_version4 blr_literal ..... blr_eoc
			fb_assert((buffer[0] == blr_version4) || (buffer[0] == blr_version5));
			fb_assert(buffer[1] == blr_literal);
		}
		else {
			if (request->req_dbb->dbb_db_SQL_dialect > SQL_DIALECT_V5)
				buffer[0] = blr_version5;
			else
				buffer[0] = blr_version4;
			buffer[1] = blr_eoc;
			result = 2;
		}

	END_FOR

	if (!DSQL_REQUEST(irq_col_default))
		DSQL_REQUEST(irq_col_default) = handle;

	return result;
}


dsql_intlsym* METD_get_charset(dsql_req* request, USHORT length, const char* name) // UTF-8
{
/**************************************
 *
 *  M E T D _ g e t _ c h a r s e t
 *
 **************************************
 *
 * Functional description
 *  Look up an international text type object.
 *  If it doesn't exist, return NULL.
 *
 **************************************/
	MutexHolder holder(request);

	thread_db* tdbb = JRD_get_thread_data();

	// Start by seeing if symbol is already defined

	dsql_sym* symbol = lookup_symbol(request->req_dbb, length, name, SYM_intlsym_charset);
	if (symbol)
		return (dsql_intlsym*) symbol->sym_object;

	// Now see if it is in the database

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;
	dsql_intlsym* iname = NULL;

	jrd_req* handle = CMP_find_request(tdbb, irq_charset, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
	X IN RDB$COLLATIONS
		CROSS Y IN RDB$CHARACTER_SETS OVER RDB$CHARACTER_SET_ID
		CROSS Z IN RDB$TYPES
		WITH Z.RDB$TYPE EQ Y.RDB$CHARACTER_SET_ID
		AND Z.RDB$TYPE_NAME EQ name
		AND Z.RDB$FIELD_NAME EQ "RDB$CHARACTER_SET_NAME"
		AND Y.RDB$DEFAULT_COLLATE_NAME EQ X.RDB$COLLATION_NAME;

		if (!DSQL_REQUEST(irq_charset))
			DSQL_REQUEST(irq_charset) = handle;

		iname = FB_NEW_RPT(dbb->dbb_pool, length) dsql_intlsym;
		strcpy(iname->intlsym_name, name);
		iname->intlsym_flags = 0;
		iname->intlsym_charset_id = X.RDB$CHARACTER_SET_ID;
		iname->intlsym_collate_id = X.RDB$COLLATION_ID;
		iname->intlsym_ttype =
			INTL_CS_COLL_TO_TTYPE(iname->intlsym_charset_id, iname->intlsym_collate_id);
		iname->intlsym_bytes_per_char =
			(Y.RDB$BYTES_PER_CHARACTER.NULL) ? 1 : (Y.RDB$BYTES_PER_CHARACTER);

	END_FOR

	if (!DSQL_REQUEST(irq_charset))
		DSQL_REQUEST(irq_charset) = handle;

	if (!iname)
		return NULL;

	// Store in the symbol table

	symbol = iname->intlsym_symbol = FB_NEW_RPT(dbb->dbb_pool, 0) dsql_sym;
	symbol->sym_object = iname;
	symbol->sym_string = iname->intlsym_name;
	symbol->sym_length = length;
	symbol->sym_type = SYM_intlsym_charset;
	symbol->sym_dbb = dbb;
	insert_symbol(symbol);

	dbb->dbb_charsets_by_id.add(iname);

	return iname;
}


USHORT METD_get_charset_bpc(dsql_req* request, SSHORT charset_id)
{
/**************************************
 *
 *  M E T D _ g e t _ c h a r s e t _ b p c
 *
 **************************************
 *
 * Functional description
 *  Look up an international text type object.
 *  If it doesn't exist, return NULL.
 *  Go directly to system tables & return only the
 *  number of bytes per character. Lookup by
 *  charset' id, not by name.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	dsql_dbb* dbb = request->req_dbb;

	if (charset_id == CS_dynamic)
		charset_id = tdbb->getAttachment()->att_charset;

	dsql_intlsym* cs_sym = 0;
	size_t pos = 0;
	if (dbb->dbb_charsets_by_id.find(charset_id, pos)) {
		cs_sym = dbb->dbb_charsets_by_id[pos];
	}
	else {
		const MetaName cs_name = METD_get_charset_name(request, charset_id);
		cs_sym = METD_get_charset(request, cs_name.length(), cs_name.c_str());
	}

	fb_assert(cs_sym);

	return cs_sym ? cs_sym->intlsym_bytes_per_char : 0;
}


MetaName METD_get_charset_name(dsql_req* request, SSHORT charset_id)
{
/**************************************
 *
 *  M E T D _ g e t _ c h a r s e t _ n a m e
 *
 **************************************
 *
 * Functional description
 *  Look up an international text type object.
 *  If it doesn't exist, return empty string.
 *  Go directly to system tables & return only the
 *  name.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	dsql_dbb* dbb = request->req_dbb;

	if (charset_id == CS_dynamic)
		charset_id = tdbb->getAttachment()->att_charset;

	size_t pos = 0;
	if (dbb->dbb_charsets_by_id.find(charset_id, pos))
	{
		return dbb->dbb_charsets_by_id[pos]->intlsym_name;
	}

	validateTransaction(request);

	MetaName name;

	jrd_req* handle = CMP_find_request(tdbb, irq_cs_name, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		Y IN RDB$CHARACTER_SETS
		WITH Y.RDB$CHARACTER_SET_ID EQ charset_id

		if (!DSQL_REQUEST(irq_cs_name))
			DSQL_REQUEST(irq_cs_name) = handle;

		name = Y.RDB$CHARACTER_SET_NAME;

	END_FOR

	if (!DSQL_REQUEST(irq_cs_name))
		DSQL_REQUEST(irq_cs_name) = handle;

	// put new charset into hash table if needed
	METD_get_charset(request, name.length(), name.c_str());

	return name;
}


dsql_str* METD_get_default_charset(dsql_req* request)
{
/**************************************
 *
 *  M E T D _ g e t _ d e f a u l t _ c h a r s e t
 *
 **************************************
 *
 * Functional description
 *  Find the default character set for a database
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	dsql_dbb* dbb = request->req_dbb;
	if (dbb->dbb_no_charset)
		return NULL;

	if (dbb->dbb_dfl_charset)
		return dbb->dbb_dfl_charset;

	// Now see if it is in the database

	validateTransaction(request);

	jrd_req* handle = CMP_find_request(tdbb, irq_default_cs, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		FIRST 1 DBB IN RDB$DATABASE
		WITH DBB.RDB$CHARACTER_SET_NAME NOT MISSING;

		if (!DSQL_REQUEST(irq_default_cs))
			DSQL_REQUEST(irq_default_cs) = handle;

		// Terminate ASCIIZ string on first trailing blank
		fb_utils::exact_name(DBB.RDB$CHARACTER_SET_NAME);
		const USHORT length = strlen(DBB.RDB$CHARACTER_SET_NAME);
		dbb->dbb_dfl_charset = FB_NEW_RPT(dbb->dbb_pool, length) dsql_str;
		dbb->dbb_dfl_charset->str_length = length;
		dbb->dbb_dfl_charset->str_charset = NULL;
		memcpy(dbb->dbb_dfl_charset->str_data, DBB.RDB$CHARACTER_SET_NAME, length);

	END_FOR

	if (!DSQL_REQUEST(irq_default_cs))
		DSQL_REQUEST(irq_default_cs) = handle;

	if (!dbb->dbb_dfl_charset)
		dbb->dbb_no_charset = true;

	return dbb->dbb_dfl_charset;
}


bool METD_get_domain(dsql_req* request, dsql_fld* field, const char* name) // UTF-8
{
/**************************************
 *
 *  M E T D _ g e t _ d o m a i n
 *
 **************************************
 *
 * Functional description
 *  Fetch domain information for field defined as 'name'
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(request);

	bool found = false;

	dsql_dbb* dbb = request->req_dbb;

	jrd_req* handle = CMP_find_request(tdbb, irq_domain, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		FLX IN RDB$FIELDS WITH FLX.RDB$FIELD_NAME EQ name

		if (!DSQL_REQUEST(irq_domain))
			DSQL_REQUEST(irq_domain) = handle;

		found = true;
		field->fld_length = FLX.RDB$FIELD_LENGTH;
		field->fld_scale = FLX.RDB$FIELD_SCALE;
		field->fld_sub_type = FLX.RDB$FIELD_SUB_TYPE;
		field->fld_dimensions = FLX.RDB$DIMENSIONS.NULL ? 0 : FLX.RDB$DIMENSIONS;

		field->fld_character_set_id = 0;
		if (!FLX.RDB$CHARACTER_SET_ID.NULL)
			field->fld_character_set_id = FLX.RDB$CHARACTER_SET_ID;
		field->fld_collation_id = 0;
		if (!FLX.RDB$COLLATION_ID.NULL)
			field->fld_collation_id = FLX.RDB$COLLATION_ID;
		field->fld_character_length = 0;
		if (!FLX.RDB$CHARACTER_LENGTH.NULL)
			field->fld_character_length = FLX.RDB$CHARACTER_LENGTH;

		if (!FLX.RDB$COMPUTED_BLR.NULL)
			field->fld_flags |= FLD_computed;

		if (FLX.RDB$SYSTEM_FLAG == 1)
			field->fld_flags |= FLD_system;

		convert_dtype(field, FLX.RDB$FIELD_TYPE);

		if (FLX.RDB$FIELD_TYPE == blr_blob) {
			field->fld_seg_length = FLX.RDB$SEGMENT_LENGTH;
		}

	END_FOR

	if (!DSQL_REQUEST(irq_domain))
		DSQL_REQUEST(irq_domain) = handle;

	return found;
}


USHORT METD_get_domain_default(dsql_req* request,
							   const TEXT* domain_name,
							   bool* has_default,
							   UCHAR* buffer,
							   USHORT buff_length)
{
/*************************************************************
 *
 *  M E T D _ g e t _ d o m a i n _ d e f a u l t
 *
 **************************************************************
 *
 * Function:
 *    Gets the default value for a domain of an existing table.
 *
 **************************************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(request);

	*has_default = false;

	dsql_dbb* dbb = request->req_dbb;
	USHORT result = 0;

	jrd_req* handle = CMP_find_request(tdbb, irq_domain_2, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ domain_name

		if (!DSQL_REQUEST(irq_domain_2))
			DSQL_REQUEST(irq_domain_2) = handle;

		bid* blob_id;
		if (!FLD.RDB$DEFAULT_VALUE.NULL) {
			blob_id = &FLD.RDB$DEFAULT_VALUE;
			*has_default = true;
		}
		else
			*has_default = false;

		if (*has_default) {
			blb* blob_handle = BLB_open2(tdbb, request->req_transaction, blob_id,
										 sizeof(blr_bpb), blr_bpb, true);

			// fetch segments. Assume buffer is big enough.
			UCHAR* ptr_in_buffer = buffer;
			while (true)
			{
				const USHORT length = BLB_get_segment(tdbb, blob_handle, ptr_in_buffer, buff_length);

				ptr_in_buffer += length;
				buff_length -= length;
				result += length;

				if (blob_handle->blb_flags & BLB_eof)
				{
					// null terminate the buffer
					*ptr_in_buffer = 0;
					break;
				}
				if (blob_handle->blb_fragment_size)
					status_exception::raise(Arg::Gds(isc_segment));
				else
					continue;
			}

			try
			{
				ThreadStatusGuard status_vector(tdbb);

				BLB_close(tdbb, blob_handle);
				blob_handle = NULL;
			}
			catch (Exception&)
			{
			}

			// the default string must be of the form:
			// blr_version4 blr_literal ..... blr_eoc
			fb_assert((buffer[0] == blr_version4) || (buffer[0] == blr_version5));
			fb_assert(buffer[1] == blr_literal);
		}
		else {
			if (request->req_dbb->dbb_db_SQL_dialect > SQL_DIALECT_V5)
				buffer[0] = blr_version5;
			else
				buffer[0] = blr_version4;
			buffer[1] = blr_eoc;
			result = 2;
		}

	END_FOR

	if (!DSQL_REQUEST(irq_domain_2))
		DSQL_REQUEST(irq_domain_2) = handle;

	return result;
}


bool METD_get_exception(dsql_req* request, const dsql_str* name)
{
/**************************************
 *
 *  M E T D _ g e t _ e x c e p t i o n
 *
 **************************************
 *
 * Functional description
 *  Look up an exception.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;
	bool found = false;

	jrd_req* handle = CMP_find_request(tdbb, irq_exception, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		X IN RDB$EXCEPTIONS WITH
		X.RDB$EXCEPTION_NAME EQ name->str_data;

		if (!DSQL_REQUEST(irq_exception))
			DSQL_REQUEST(irq_exception) = handle;

		found = true;

	END_FOR

	if (!DSQL_REQUEST(irq_exception))
		DSQL_REQUEST(irq_exception) = handle;

	return found;
}


dsql_udf* METD_get_function(dsql_req* request, const dsql_str* name)
{
/**************************************
 *
 *  M E T D _ g e t _ f u n c t i o n
 *
 **************************************
 *
 * Functional description
 *  Look up a user defined function.  If it doesn't exist,
 *  return NULL.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	MutexHolder holder(request);

	// Start by seeing if symbol is already defined

	dsql_sym* symbol = lookup_symbol(request->req_dbb, name, SYM_udf);
	if (symbol)
		return (dsql_udf*) symbol->sym_object;

	// Now see if it is in the database

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;
	dsql_udf* userFunc = NULL;
	USHORT return_arg = 0;

	jrd_req* handle1 = CMP_find_request(tdbb, irq_function, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE request->req_transaction)
		X IN RDB$FUNCTIONS WITH
		X.RDB$FUNCTION_NAME EQ name->str_data

		if (!DSQL_REQUEST(irq_function))
			DSQL_REQUEST(irq_function) = handle1;

		userFunc = FB_NEW(dbb->dbb_pool) dsql_udf(dbb->dbb_pool);
		// Moved below as still can't say for sure it will be stored.
		// Following the same logic for MET_get_procedure and MET_get_relation
		// userFunc->udf_next = dbb->dbb_functions;
		// dbb->dbb_functions = userFunc;

		userFunc->udf_name = name->str_data;
		return_arg = X.RDB$RETURN_ARGUMENT;

	END_FOR

	if (!DSQL_REQUEST(irq_function))
		DSQL_REQUEST(irq_function) = handle1;

	if (!userFunc) {
		return NULL;
	}

	// Note: The following two requests differ in the fields which are
	// new since ODS7 (DBB_v3 flag).  One of the two requests
	// here will be cached in dbb_internal[ird_func_return],
	// the one that is appropriate for the database we are
	// working against.

	jrd_req* handle2 = CMP_find_request(tdbb, irq_func_return, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE request->req_transaction)
		X IN RDB$FUNCTION_ARGUMENTS WITH
		X.RDB$FUNCTION_NAME EQ name->str_data
		SORTED BY X.RDB$ARGUMENT_POSITION

		if (!DSQL_REQUEST(irq_func_return))
			DSQL_REQUEST(irq_func_return) = handle2;

		if (X.RDB$ARGUMENT_POSITION == return_arg) {
			userFunc->udf_dtype = (X.RDB$FIELD_TYPE != blr_blob) ?
				gds_cvt_blr_dtype[X.RDB$FIELD_TYPE] : dtype_blob;
			userFunc->udf_scale = X.RDB$FIELD_SCALE;

			if (!X.RDB$FIELD_SUB_TYPE.NULL) {
				userFunc->udf_sub_type = X.RDB$FIELD_SUB_TYPE;
			}
			else {
				userFunc->udf_sub_type = 0;
			}
			// CVC: We are overcoming a bug in ddl.c:put_field()
			// when any field is defined: the length is not given for blobs.
			if (X.RDB$FIELD_TYPE == blr_blob)
				userFunc->udf_length = sizeof(ISC_QUAD);
			else
				userFunc->udf_length = X.RDB$FIELD_LENGTH;

			if (!X.RDB$CHARACTER_SET_ID.NULL) {
				userFunc->udf_character_set_id = X.RDB$CHARACTER_SET_ID;
			}
		}
		else {
			DSC d;
			d.dsc_dtype = (X.RDB$FIELD_TYPE != blr_blob) ?
				gds_cvt_blr_dtype[X.RDB$FIELD_TYPE] : dtype_blob;
			// dimitr: adjust the UDF arguments for CSTRING
			if (d.dsc_dtype == dtype_cstring) {
				d.dsc_dtype = dtype_text;
			}
			d.dsc_scale = X.RDB$FIELD_SCALE;
			if (!X.RDB$FIELD_SUB_TYPE.NULL) {
				d.dsc_sub_type = X.RDB$FIELD_SUB_TYPE;
			}
			else {
				d.dsc_sub_type = 0;
			}
			d.dsc_length = X.RDB$FIELD_LENGTH;
			if (d.dsc_dtype == dtype_varying) {
				d.dsc_length += sizeof(USHORT);
			}
			d.dsc_address = NULL;

			if (!X.RDB$CHARACTER_SET_ID.NULL) {
				if (d.dsc_dtype != dtype_blob) {
					d.dsc_ttype() = X.RDB$CHARACTER_SET_ID;
				}
				else {
					d.dsc_scale = X.RDB$CHARACTER_SET_ID;
				}
			}

			if (X.RDB$MECHANISM != FUN_value && X.RDB$MECHANISM != FUN_reference)
			{
				d.dsc_flags = DSC_nullable;
			}

			userFunc->udf_arguments.add(d);
		}

	END_FOR

	if (!DSQL_REQUEST(irq_func_return))
		DSQL_REQUEST(irq_func_return) = handle2;

	// Adjust the return type & length of the UDF to account for
	// cstring & varying.  While a UDF can return CSTRING, we convert it
	// to VARCHAR for manipulation as CSTRING is not a SQL type.

	if (userFunc->udf_dtype == dtype_cstring) {
		userFunc->udf_dtype = dtype_varying;
		userFunc->udf_length += sizeof(USHORT);
		if (userFunc->udf_length > MAX_SSHORT)
			userFunc->udf_length = MAX_SSHORT;
	}
	else if (userFunc->udf_dtype == dtype_varying)
		userFunc->udf_length += sizeof(USHORT);

	if ((symbol = lookup_symbol(request->req_dbb, name, SYM_udf)))
	{
		// Get rid of all the stuff we just read in. Use existing one
		delete userFunc;
		return (dsql_udf*) symbol->sym_object;
	}

	// Add udf in the front of the list.
	userFunc->udf_next = dbb->dbb_functions;
	dbb->dbb_functions = userFunc;

	// Store in the symbol table
	// The UDF_new_udf flag is not used, so nothing extra to check.

	symbol = userFunc->udf_symbol = FB_NEW_RPT(dbb->dbb_pool, 0) dsql_sym;
	symbol->sym_object = userFunc;
	symbol->sym_string = userFunc->udf_name.c_str();
	symbol->sym_length = userFunc->udf_name.length();
	symbol->sym_type = SYM_udf;
	symbol->sym_dbb = dbb;
	insert_symbol(symbol);

	return userFunc;
}


dsql_nod* METD_get_primary_key(dsql_req* request, const dsql_str* relation_name)
{
/**************************************
 *
 *  M E T D _ g e t _ p r i m a r y _ k e y
 *
 **************************************
 *
 * Functional description
 *  Lookup the fields for the primary key
 *  index on a relation, returning a list
 *  node of the fields.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;

	DsqlNodStack stack;

	jrd_req* handle = CMP_find_request(tdbb, irq_primary_key, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		X IN RDB$INDICES CROSS
		Y IN RDB$INDEX_SEGMENTS
		OVER RDB$INDEX_NAME CROSS
		Z IN RDB$RELATION_CONSTRAINTS
		OVER RDB$INDEX_NAME
		WITH Z.RDB$RELATION_NAME EQ relation_name->str_data
		AND Z.RDB$CONSTRAINT_TYPE EQ "PRIMARY KEY"
		SORTED BY Y.RDB$FIELD_POSITION

		if (!DSQL_REQUEST(irq_primary_key))
			DSQL_REQUEST(irq_primary_key) = handle;

		stack.push(MAKE_field_name(Y.RDB$FIELD_NAME));

	END_FOR

	if (!DSQL_REQUEST(irq_primary_key))
		DSQL_REQUEST(irq_primary_key) = handle;

	return (stack.getCount() ? MAKE_list(stack) : NULL);
}


dsql_prc* METD_get_procedure(CompiledStatement* statement, const dsql_str* name)
{
/**************************************
 *
 *  M E T D _ g e t _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *  Look up a procedure.  If it doesn't exist, return NULL.
 *  If it does, fetch field information as well.
 *  If it is marked dropped, try to read from system tables
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	dsql_dbb* dbb = statement->req_dbb;

	// see if the procedure is the one currently being defined in this statement

	dsql_prc* temp = statement->req_procedure;
	if (temp != NULL && temp->prc_name == name->str_data)
	{
		return temp;
	}

	MutexHolder holder(statement);

	// Start by seeing if symbol is already defined

	dsql_sym* symbol = lookup_symbol(statement->req_dbb, name, SYM_procedure);
	if (symbol)
		return (dsql_prc*) symbol->sym_object;

	// now see if it is in the database

	validateTransaction(statement);

	dsql_prc* procedure = NULL;

	jrd_req* handle1 = CMP_find_request(tdbb, irq_procedure, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE statement->req_transaction)
		X IN RDB$PROCEDURES WITH
		X.RDB$PROCEDURE_NAME EQ name->str_data

		if (!DSQL_REQUEST(irq_procedure))
			DSQL_REQUEST(irq_procedure) = handle1;

		fb_utils::exact_name(X.RDB$OWNER_NAME);

		procedure = FB_NEW(dbb->dbb_pool) dsql_prc(dbb->dbb_pool);
		procedure->prc_id = X.RDB$PROCEDURE_ID;

		procedure->prc_name = name->str_data;
		procedure->prc_owner = X.RDB$OWNER_NAME;

	END_FOR

	if (!DSQL_REQUEST(irq_procedure))
		DSQL_REQUEST(irq_procedure) = handle1;

	if (!procedure) {
		return NULL;
	}

	// Lookup parameter stuff

	for (int type = 0; type < 2; type++)
	{
		dsql_fld** ptr;
		if (type)
			ptr = &procedure->prc_outputs;
		else
			ptr = &procedure->prc_inputs;

		SSHORT count = 0, defaults = 0;

		jrd_req* handle2 = CMP_find_request(tdbb, irq_parameters, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE statement->req_transaction)
			PR IN RDB$PROCEDURE_PARAMETERS CROSS
			FLD IN RDB$FIELDS
			WITH FLD.RDB$FIELD_NAME EQ PR.RDB$FIELD_SOURCE
			AND PR.RDB$PROCEDURE_NAME EQ name->str_data
			AND PR.RDB$PARAMETER_TYPE = type
			SORTED BY DESCENDING PR.RDB$PARAMETER_NUMBER

			if (!DSQL_REQUEST(irq_parameters))
				DSQL_REQUEST(irq_parameters) = handle2;

			SSHORT pr_collation_id_null = TRUE;
			SSHORT pr_collation_id;
			SSHORT pr_default_value_null = TRUE;
			SSHORT pr_null_flag_null = TRUE;
			SSHORT pr_null_flag;
			bool pr_type_of = false;

			if (ENCODE_ODS(dbb->dbb_ods_version, dbb->dbb_minor_version) >= ODS_11_1)
			{
				jrd_req* handle3 = CMP_find_request(tdbb, irq_parameters2, IRQ_REQUESTS);

				FOR(REQUEST_HANDLE handle3 TRANSACTION_HANDLE statement->req_transaction)
					PR2 IN RDB$PROCEDURE_PARAMETERS
					WITH PR2.RDB$PROCEDURE_NAME EQ PR.RDB$PROCEDURE_NAME AND
						 PR2.RDB$PARAMETER_NAME EQ PR.RDB$PARAMETER_NAME

					if (!DSQL_REQUEST(irq_parameters2))
						DSQL_REQUEST(irq_parameters2) = handle3;

					pr_collation_id_null = PR2.RDB$COLLATION_ID.NULL;
					pr_collation_id = PR2.RDB$COLLATION_ID;

					pr_default_value_null = PR2.RDB$DEFAULT_VALUE.NULL;

					pr_null_flag_null = PR2.RDB$NULL_FLAG.NULL;
					pr_null_flag = PR2.RDB$NULL_FLAG;

					if (!PR2.RDB$PARAMETER_MECHANISM.NULL && PR2.RDB$PARAMETER_MECHANISM == prm_mech_type_of)
						pr_type_of = true;

				END_FOR

				if (!DSQL_REQUEST(irq_parameters2))
					DSQL_REQUEST(irq_parameters2) = handle3;
			}

			count++;
			// allocate the field block

			fb_utils::exact_name(PR.RDB$PARAMETER_NAME);
			fb_utils::exact_name(PR.RDB$FIELD_SOURCE);

			dsql_fld* parameter = FB_NEW(dbb->dbb_pool) dsql_fld(dbb->dbb_pool);
			parameter->fld_next = *ptr;
			*ptr = parameter;

			// get parameter information

			parameter->fld_name = PR.RDB$PARAMETER_NAME;
			parameter->fld_source = PR.RDB$FIELD_SOURCE;

			parameter->fld_id = PR.RDB$PARAMETER_NUMBER;
			parameter->fld_length = FLD.RDB$FIELD_LENGTH;
			parameter->fld_scale = FLD.RDB$FIELD_SCALE;
			parameter->fld_sub_type = FLD.RDB$FIELD_SUB_TYPE;
			parameter->fld_procedure = procedure;

			if (!FLD.RDB$CHARACTER_SET_ID.NULL)
				parameter->fld_character_set_id = FLD.RDB$CHARACTER_SET_ID;

			if (!pr_collation_id_null)
				parameter->fld_collation_id = pr_collation_id;
			else if (!FLD.RDB$COLLATION_ID.NULL)
				parameter->fld_collation_id = FLD.RDB$COLLATION_ID;

			convert_dtype(parameter, FLD.RDB$FIELD_TYPE);

			if (!pr_null_flag_null)
			{
				if (!pr_null_flag)
					parameter->fld_flags |= FLD_nullable;
			}
			else if (!FLD.RDB$NULL_FLAG || pr_type_of)
				parameter->fld_flags |= FLD_nullable;

			if (FLD.RDB$FIELD_TYPE == blr_blob)
				parameter->fld_seg_length = FLD.RDB$SEGMENT_LENGTH;

			if (!PR.RDB$FIELD_NAME.NULL)
			{
				fb_utils::exact_name(PR.RDB$FIELD_NAME);
				parameter->fld_type_of_name = PR.RDB$FIELD_NAME;
			}

			if (!PR.RDB$RELATION_NAME.NULL)
			{
				fb_utils::exact_name(PR.RDB$RELATION_NAME);
				parameter->fld_type_of_table = PR.RDB$RELATION_NAME;
			}

			if (type == 0 &&
				(!pr_default_value_null ||
					(fb_utils::implicit_domain(FLD.RDB$FIELD_NAME) && !FLD.RDB$DEFAULT_VALUE.NULL)))
			{
				defaults++;
			}

		END_FOR

		if (!DSQL_REQUEST(irq_parameters))
			DSQL_REQUEST(irq_parameters) = handle2;

		if (type)
			procedure->prc_out_count = count;
		else {
			procedure->prc_in_count = count;
			procedure->prc_def_count = defaults;
		}
	}

	// Since we could give up control due to the THREAD_EXIT and THEAD_ENTER
	// calls, another thread may have added the same procedure in the mean time

	if ((symbol = lookup_symbol(statement->req_dbb, name, SYM_procedure)))
	{
		// Get rid of all the stuff we just read in. Use existing one
		free_procedure(procedure);
		return (dsql_prc*) symbol->sym_object;
	}

	// store in the symbol table unless the procedure is not yet committed
	// CVC: This is strange, because PRC_new_procedure is never set.

	if (!(procedure->prc_flags & PRC_new_procedure))
	{
		// add procedure in the front of the list

		procedure->prc_next = dbb->dbb_procedures;
		dbb->dbb_procedures = procedure;

		symbol = procedure->prc_symbol = FB_NEW_RPT(dbb->dbb_pool, 0) dsql_sym;
		symbol->sym_object = procedure;
		symbol->sym_string = procedure->prc_name.c_str();
		symbol->sym_length = procedure->prc_name.length();
		symbol->sym_type = SYM_procedure;
		symbol->sym_dbb = dbb;
		insert_symbol(symbol);
	}

	return procedure;
}


// Get the description of a procedure parameter. The description is returned in the client charset.
void METD_get_procedure_parameter(CompiledStatement* statement, const MetaName& procedure,
	const MetaName& parameter, UCharBuffer& buffer)
{
	thread_db* tdbb = JRD_get_thread_data();

	dsql_dbb* dbb = statement->req_dbb;

	MutexHolder holder(statement);

	validateTransaction(statement);

	buffer.clear();

	jrd_req* handle = CMP_find_request(tdbb, irq_parameters_description, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE statement->req_transaction)
		X IN RDB$PROCEDURE_PARAMETERS
		WITH X.RDB$PROCEDURE_NAME EQ procedure.c_str() AND
			 X.RDB$PARAMETER_NAME EQ parameter.c_str() AND
			 X.RDB$DESCRIPTION NOT MISSING
	{
		if (!DSQL_REQUEST(irq_parameters_description))
			DSQL_REQUEST(irq_parameters_description) = handle;

		UCharBuffer bpb;
		BLB_gen_bpb(isc_blob_text, isc_blob_text, CS_METADATA, CS_dynamic, bpb);

		blb* blob = BLB_open2(tdbb, statement->req_transaction, &X.RDB$DESCRIPTION,
			bpb.getCount(), bpb.begin());
		buffer.resize(blob->blb_length);
		BLB_get_data(tdbb, blob, buffer.begin(), buffer.getCount());
	}
	END_FOR

	if (!DSQL_REQUEST(irq_parameters_description))
		DSQL_REQUEST(irq_parameters_description) = handle;
}


dsql_rel* METD_get_relation(CompiledStatement* statement, const char* name)
{
/**************************************
 *
 *  M E T D _ g e t _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *  Look up a relation.  If it doesn't exist, return NULL.
 *  If it does, fetch field information as well.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	dsql_dbb* dbb = statement->req_dbb;

	// See if the relation is the one currently being defined in this statement

	dsql_rel* temp = statement->req_relation;
	if (temp != NULL && temp->rel_name == name)
	{
		return temp;
	}

	MutexHolder holder(statement);

	// Start by seeing if symbol is already defined

	dsql_sym* symbol = lookup_symbol(statement->req_dbb, strlen(name), name, SYM_relation);
	if (symbol)
		return (dsql_rel*) symbol->sym_object;

	// If the relation id or any of the field ids have not yet been assigned,
	// and this is a type of statement which does not use ids, prepare a
	// temporary relation block to provide information without caching it

	validateTransaction(statement);

	bool permanent = true;

	jrd_req* handle1 = CMP_find_request(tdbb, irq_rel_ids, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE statement->req_transaction)
		REL IN RDB$RELATIONS
		CROSS RFR IN RDB$RELATION_FIELDS OVER RDB$RELATION_NAME
		WITH REL.RDB$RELATION_NAME EQ name
		AND (REL.RDB$RELATION_ID MISSING OR RFR.RDB$FIELD_ID MISSING)

		if (!DSQL_REQUEST(irq_rel_ids))
			DSQL_REQUEST(irq_rel_ids) = handle1;

		permanent = false;

	END_FOR

	if (!DSQL_REQUEST(irq_rel_ids))
		DSQL_REQUEST(irq_rel_ids) = handle1;

	// Now see if it is in the database

	MemoryPool& pool = permanent ? dbb->dbb_pool : *tdbb->getDefaultPool();

	dsql_rel* relation = NULL;

	jrd_req* handle2 = CMP_find_request(tdbb, irq_relation, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE statement->req_transaction)
		X IN RDB$RELATIONS WITH X.RDB$RELATION_NAME EQ name

		if (!DSQL_REQUEST(irq_relation))
			DSQL_REQUEST(irq_relation) = handle2;

		fb_utils::exact_name(X.RDB$OWNER_NAME);

		// Allocate from default or permanent pool as appropriate

		if (!X.RDB$RELATION_ID.NULL) {
			relation = FB_NEW(pool) dsql_rel(pool);
			relation->rel_id = X.RDB$RELATION_ID;
		}
		else if (!DDL_ids(statement)) {
			relation = FB_NEW(pool) dsql_rel(pool);
		}

		// fill out the relation information

		if (relation) {
			relation->rel_name = name;
			relation->rel_owner = X.RDB$OWNER_NAME;
			if (!(relation->rel_dbkey_length = X.RDB$DBKEY_LENGTH))
				relation->rel_dbkey_length = 8;
			// CVC: let's see if this is a table or a view.
			if (!X.RDB$VIEW_BLR.NULL) {
				relation->rel_flags |= REL_view;
			}
			if (!X.RDB$EXTERNAL_FILE.NULL) {
				relation->rel_flags |= REL_external;
			}
		}

	END_FOR

	if (!DSQL_REQUEST(irq_relation))
		DSQL_REQUEST(irq_relation) = handle2;

	if (!relation) {
		return NULL;
	}

	// Lookup field stuff

	dsql_fld** ptr = &relation->rel_fields;

	jrd_req* handle3 = CMP_find_request(tdbb, irq_fields, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle3 TRANSACTION_HANDLE statement->req_transaction)
		FLX IN RDB$FIELDS CROSS
		RFR IN RDB$RELATION_FIELDS
		WITH FLX.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE
		AND RFR.RDB$RELATION_NAME EQ name
		SORTED BY RFR.RDB$FIELD_POSITION

		if (!DSQL_REQUEST(irq_fields))
			DSQL_REQUEST(irq_fields) = handle3;

		// allocate the field block

		fb_utils::exact_name(RFR.RDB$FIELD_NAME);
		fb_utils::exact_name(RFR.RDB$FIELD_SOURCE);

		// Allocate from default or permanent pool as appropriate

		dsql_fld* field = NULL;

		if (!RFR.RDB$FIELD_ID.NULL) {
			field = FB_NEW(pool) dsql_fld(pool);
			field->fld_id = RFR.RDB$FIELD_ID;
		}
		else if (!DDL_ids(statement))
			field = FB_NEW(pool) dsql_fld(pool);

		if (field) {
			*ptr = field;
			ptr = &field->fld_next;

			// get field information

			field->fld_name = RFR.RDB$FIELD_NAME;
			field->fld_source = RFR.RDB$FIELD_SOURCE;
			field->fld_length = FLX.RDB$FIELD_LENGTH;
			field->fld_scale = FLX.RDB$FIELD_SCALE;
			field->fld_sub_type = FLX.RDB$FIELD_SUB_TYPE;
			field->fld_relation = relation;

			if (!FLX.RDB$COMPUTED_BLR.NULL)
				field->fld_flags |= FLD_computed;

			convert_dtype(field, FLX.RDB$FIELD_TYPE);

			if (FLX.RDB$FIELD_TYPE == blr_blob) {
				field->fld_seg_length = FLX.RDB$SEGMENT_LENGTH;
			}

			if (!FLX.RDB$DIMENSIONS.NULL && FLX.RDB$DIMENSIONS)
			{
				field->fld_element_dtype = field->fld_dtype;
				field->fld_element_length = field->fld_length;
				field->fld_dtype = dtype_array;
				field->fld_length = sizeof(ISC_QUAD);
				field->fld_dimensions = FLX.RDB$DIMENSIONS;
			}

			if (!FLX.RDB$CHARACTER_SET_ID.NULL)
				field->fld_character_set_id = FLX.RDB$CHARACTER_SET_ID;

			if (!RFR.RDB$COLLATION_ID.NULL)
				field->fld_collation_id = RFR.RDB$COLLATION_ID;
			else if (!FLX.RDB$COLLATION_ID.NULL)
				field->fld_collation_id = FLX.RDB$COLLATION_ID;

			if (!(RFR.RDB$NULL_FLAG || FLX.RDB$NULL_FLAG) || (relation->rel_flags & REL_view))
			{
				field->fld_flags |= FLD_nullable;
			}

			if (RFR.RDB$SYSTEM_FLAG == 1 || FLX.RDB$SYSTEM_FLAG == 1)
				field->fld_flags |= FLD_system;
		}

	END_FOR

	if (!DSQL_REQUEST(irq_fields))
		DSQL_REQUEST(irq_fields) = handle3;

	if ((symbol = lookup_symbol(statement->req_dbb, strlen(name), name, SYM_relation)))
	{
		free_relation(relation);
		return (dsql_rel*) symbol->sym_object;
	}

	// Add relation to front of list

	if (permanent) {
		relation->rel_next = dbb->dbb_relations;
		dbb->dbb_relations = relation;

		// store in the symbol table unless the relation is not yet committed

		symbol = relation->rel_symbol = FB_NEW_RPT(dbb->dbb_pool, 0) dsql_sym;
		symbol->sym_object = relation;
		symbol->sym_string = relation->rel_name.c_str();
		symbol->sym_length = relation->rel_name.length();
		symbol->sym_type = SYM_relation;
		symbol->sym_dbb = dbb;
		insert_symbol(symbol);
	}
	else {
		relation->rel_flags |= REL_new_relation;
	}

	return relation;
}


bool METD_get_trigger(dsql_req* request, const dsql_str* name, dsql_str** relation, USHORT* trig_type)
{
/**************************************
 *
 *  M E T D _ g e t _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *  Look up a trigger's relation and it's current type
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;

	bool found = false;

	if (relation)
		*relation = NULL;

	jrd_req* handle = CMP_find_request(tdbb, irq_trigger, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		X IN RDB$TRIGGERS WITH X.RDB$TRIGGER_NAME EQ name->str_data

		if (!DSQL_REQUEST(irq_trigger))
			DSQL_REQUEST(irq_trigger) = handle;

		found = true;
		*trig_type = X.RDB$TRIGGER_TYPE;

		if (!X.RDB$RELATION_NAME.NULL)
		{
			if (relation)
			{
				fb_utils::exact_name(X.RDB$RELATION_NAME);
				*relation = MAKE_string(X.RDB$RELATION_NAME, strlen(X.RDB$RELATION_NAME));
			}
		}

	END_FOR

	if (!DSQL_REQUEST(irq_trigger))
		DSQL_REQUEST(irq_trigger) = handle;

	return found;
}


bool METD_get_type(dsql_req* request, const dsql_str* name, const char* field, SSHORT* value)
{
/**************************************
 *
 *  M E T D _ g e t _ t y p e
 *
 **************************************
 *
 * Functional description
 *  Look up a symbolic name in RDB$TYPES
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(request);

	dsql_dbb* dbb = request->req_dbb;
	bool found = false;

	jrd_req* handle = CMP_find_request(tdbb, irq_type, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE request->req_transaction)
		X IN RDB$TYPES WITH
		X.RDB$FIELD_NAME EQ field AND X.RDB$TYPE_NAME EQ name->str_data;

		if (!DSQL_REQUEST(irq_type))
			DSQL_REQUEST(irq_type) = handle;

		found = true;
		*value = X.RDB$TYPE;

	END_FOR

	if (!DSQL_REQUEST(irq_type))
		DSQL_REQUEST(irq_type) = handle;

	return found;
}


dsql_rel* METD_get_view_base(CompiledStatement* statement,
							 const char* view_name, // UTF-8
							 MetaNamePairMap& fields)
{
/**************************************
 *
 *  M E T D _ g e t _ v i e w _ b a s e
 *
 **************************************
 *
 * Functional description
 *  Return the base table of a view or NULL if there
 *  is more than one.
 *  If there is only one base, put in fields a map of
 *  top view field name / bottom base field name.
 *  Ignores the field in the case of a base field name
 *  appearing more than one time in a level.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(statement);

	dsql_rel* relation = NULL;

	dsql_dbb* dbb = statement->req_dbb;
	bool first = true;
	bool cont = true;
	MetaNamePairMap previousAux;

	fields.clear();

	while (cont)
	{
		jrd_req* handle1 = CMP_find_request(tdbb, irq_view_base, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE statement->req_transaction)
			X IN RDB$VIEW_RELATIONS
			WITH X.RDB$VIEW_NAME EQ view_name

			if (!DSQL_REQUEST(irq_view_base))
				DSQL_REQUEST(irq_view_base) = handle1;

			// return NULL if there is more than one context
			if (X.RDB$VIEW_CONTEXT != 1)
			{
				relation = NULL;
				cont = false;
				EXE_unwind(tdbb, handle1);
				break;
			}

			fb_utils::exact_name(X.RDB$CONTEXT_NAME);
			fb_utils::exact_name(X.RDB$RELATION_NAME);

			relation = METD_get_relation(statement, X.RDB$RELATION_NAME);

			Array<MetaName> ambiguities;
			MetaNamePairMap currentAux;

			if (!relation)
			{
				cont = false;
				EXE_unwind(tdbb, handle1);
				break;
			}

			jrd_req* handle2 = CMP_find_request(tdbb, irq_view_base_flds, IRQ_REQUESTS);

			FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE statement->req_transaction)
				RFL IN RDB$RELATION_FIELDS
				WITH RFL.RDB$RELATION_NAME EQ X.RDB$VIEW_NAME

				if (!DSQL_REQUEST(irq_view_base_flds))
					DSQL_REQUEST(irq_view_base_flds) = handle2;

				if (RFL.RDB$BASE_FIELD.NULL || RFL.RDB$FIELD_NAME.NULL)
					continue;

				const MetaName baseField(RFL.RDB$BASE_FIELD);
				if (currentAux.exist(baseField))
					ambiguities.add(baseField);
				else
				{
					const MetaName fieldName(RFL.RDB$FIELD_NAME);
					if (first)
					{
						fields.put(fieldName, baseField);
						currentAux.put(baseField, fieldName);
					}
					else
					{
						MetaName field;

						if (previousAux.get(fieldName, field))
						{
							fields.put(field, baseField);
							currentAux.put(baseField, field);
						}
					}
				}

			END_FOR

			if (!DSQL_REQUEST(irq_view_base_flds))
				DSQL_REQUEST(irq_view_base_flds) = handle2;

			for (const MetaName* i = ambiguities.begin(); i != ambiguities.end(); ++i)
			{
				MetaName field;

				if (currentAux.get(*i, field))
				{
					currentAux.remove(*i);
					fields.remove(field);
				}
			}

			previousAux.takeOwnership(currentAux);

			if (relation->rel_flags & REL_view)
				view_name = X.RDB$RELATION_NAME;
			else
			{
				cont = false;
				EXE_unwind(tdbb, handle1);
				break;
			}

			first = false;

		END_FOR

		if (!DSQL_REQUEST(irq_view_base))
			DSQL_REQUEST(irq_view_base) = handle1;
	}

	if (!relation)
		fields.clear();

	return relation;
}


dsql_rel* METD_get_view_relation(CompiledStatement* statement,
								const char* view_name,         // UTF-8
								const char* relation_or_alias) // UTF-8
{
/**************************************
 *
 *  M E T D _ g e t _ v i e w _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *  Return TRUE if the passed view_name represents a
 *  view with the passed relation as a base table
 *  (the relation could be an alias).
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(statement);

	dsql_rel* relation = NULL;

	dsql_dbb* dbb = statement->req_dbb;

	jrd_req* handle = CMP_find_request(tdbb, irq_view, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE statement->req_transaction)
		X IN RDB$VIEW_RELATIONS WITH X.RDB$VIEW_NAME EQ view_name

		if (!DSQL_REQUEST(irq_view))
			DSQL_REQUEST(irq_view) = handle;

		fb_utils::exact_name(X.RDB$CONTEXT_NAME);
		fb_utils::exact_name(X.RDB$RELATION_NAME);

		if (!strcmp(X.RDB$RELATION_NAME, relation_or_alias) ||
			!strcmp(X.RDB$CONTEXT_NAME, relation_or_alias))
		{
			relation = METD_get_relation(statement, X.RDB$RELATION_NAME);
			EXE_unwind(tdbb, handle);
			return relation;
		}

		relation = METD_get_view_relation(statement, X.RDB$RELATION_NAME, relation_or_alias);
		if (relation) {
			EXE_unwind(tdbb, handle);
			return relation;
		}

	END_FOR

	if (!DSQL_REQUEST(irq_view))
		DSQL_REQUEST(irq_view) = handle;

	return NULL;
}


static void convert_dtype(dsql_fld* field, SSHORT field_type)
{
/**************************************
 *
 *  c o n v e r t _ d t y p e
 *
 **************************************
 *
 * Functional description
 *  Convert from the blr_<type> stored in system metadata
 *  to the internal dtype_* descriptor.  Also set field
 *  length.
 *
 **************************************/

	// fill out the type descriptor
	switch (field_type)
	{
	case blr_text:
		field->fld_dtype = dtype_text;
		break;
	case blr_varying:
		field->fld_dtype = dtype_varying;
		field->fld_length += sizeof(USHORT);
		break;
	case blr_blob:
		field->fld_dtype = dtype_blob;
		field->fld_length = type_lengths[field->fld_dtype];
		break;
	default:
		field->fld_dtype = gds_cvt_blr_dtype[field_type];
		field->fld_length = type_lengths[field->fld_dtype];

		fb_assert(field->fld_dtype != dtype_unknown);
	}
}


static void free_procedure(dsql_prc* procedure)
{
/**************************************
 *
 *  f r e e _ p r o c e d u r e
 *
 **************************************
 *
 * Functional description
 *  Free memory allocated for a procedure block and params
 *
 **************************************/
	dsql_fld* param;

	// release the input & output parameter blocks

	for (param = procedure->prc_inputs; param;) {
		dsql_fld* temp = param;
		param = param->fld_next;
		delete temp;
	}

	for (param = procedure->prc_outputs; param;) {
		dsql_fld* temp = param;
		param = param->fld_next;
		delete temp;
	}

	// release the procedure & symbol blocks

	delete procedure;
}


static void free_relation(dsql_rel* relation)
{
/**************************************
 *
 *  f r e e _ r e l a t i o n
 *
 **************************************
 *
 * Functional description
 *  Free memory allocated for a relation block and fields
 *
 **************************************/

	// release the field blocks

	for (dsql_fld* field = relation->rel_fields; field;)
	{
		dsql_fld* temp = field;
		field = field->fld_next;
		delete temp;
	}

	// release the relation & symbol blocks

	delete relation;
}


static void insert_symbol(dsql_sym* symbol)
{
/**************************************
 *
 *  i n s e r t _ s y m b o l
 *
 **************************************
 *
 * Functional description
 *  Insert a symbol in the hash table and
 *  inform the engine that we're using it.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	HSHD_insert(symbol);

	MET_dsql_cache_use(tdbb, symbol->sym_type, symbol->sym_string);
}


static dsql_sym* lookup_symbol(dsql_dbb* dbb, USHORT length, const char* name,
	const SYM_TYPE type, USHORT charset_id)
{
/**************************************
 *
 *  l o o k u p _ s y m b o l
 *
 **************************************
 *
 * Functional description
 *  Lookup a symbol in the hash table and
 *  inform the engine that we're using it.
 *  The engine may inform that the symbol
 *  is obsolete, in this case mark it as
 *  dropped and return NULL.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	dsql_intlsym* intlSym = NULL;
	dsql_prc* procedure = NULL;
	dsql_rel* relation = NULL;
	dsql_udf* userFunc = NULL;

	dsql_sym* symbol = HSHD_lookup(dbb, name, length, type, 0);

	for (; symbol; symbol = symbol->sym_homonym)
	{
		if (symbol->sym_type == type)
		{
			if (type == SYM_intlsym_charset && (intlSym = (dsql_intlsym*) symbol->sym_object) &&
				!(intlSym->intlsym_flags & INTLSYM_dropped))
			{
				break;
			}
			else if (type == SYM_intlsym_collation &&
				(intlSym = (dsql_intlsym*) symbol->sym_object) &&
				!(intlSym->intlsym_flags & INTLSYM_dropped) &&
				(charset_id == 0 || intlSym->intlsym_charset_id == charset_id))
			{
				break;
			}
			else if (type == SYM_procedure && (procedure = (dsql_prc*) symbol->sym_object) &&
				!(procedure->prc_flags & PRC_dropped))
			{
				break;
			}
			else if (type == SYM_relation && (relation = (dsql_rel*) symbol->sym_object) &&
				!(relation->rel_flags & REL_dropped))
			{
				break;
			}
			else if (type == SYM_udf && (userFunc = (dsql_udf*) symbol->sym_object) &&
				!(userFunc->udf_flags & UDF_dropped))
			{
				break;
			}
		}
	}

	if (symbol)
	{
		bool obsolete = MET_dsql_cache_use(tdbb, type, name);

		if (obsolete)
		{
			switch (type)
			{
				case SYM_intlsym_charset:
				case SYM_intlsym_collation:
					intlSym->intlsym_flags |= INTLSYM_dropped;
					break;

				case SYM_procedure:
					procedure->prc_flags |= PRC_dropped;
					break;

				case SYM_relation:
					relation->rel_flags |= REL_dropped;
					break;

				case SYM_udf:
					userFunc->udf_flags |= UDF_dropped;
					break;

				default:
					return symbol;
			}

			symbol = NULL;
		}
	}

	return symbol;
}


static dsql_sym* lookup_symbol(dsql_dbb* dbb, const dsql_str* name, SYM_TYPE type, USHORT charset_id)
{
/**************************************
 *
 *  l o o k u p _ s y m b o l
 *
 **************************************
 *
 * Functional description
 *  Wrapper for the above function.
 *
 **************************************/
	return lookup_symbol(dbb, name->str_length, name->str_data, type, charset_id);
}
