/*
 *  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/ExprNodes.h"
#include "../dsql/ddl_proto.h"
#include "../dsql/metd_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 "../yvalve/gds_proto.h"
#include "../jrd/met_proto.h"
#include "../yvalve/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(TypeClause*, SSHORT);
static void free_relation(dsql_rel*);

namespace
{
	inline void validateTransaction(const jrd_tra* transaction)
	{
		if (!transaction || !transaction->checkHandle())
		{
			ERR_post(Arg::Gds(isc_bad_trans_handle));
		}
	}

	bool isSystemRelation(thread_db* tdbb, jrd_tra* transaction, const char* relName)
	{
		bool rc = false;

		AutoCacheRequest handle(tdbb, irq_system_relation, IRQ_REQUESTS);
		FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
			R IN RDB$RELATIONS WITH
			R.RDB$RELATION_NAME EQ relName AND
			R.RDB$SYSTEM_FLAG EQ 1
		{
			rc = true;
		}
		END_FOR

		return rc;
	}

	bool isSystemDomain(thread_db* tdbb, jrd_tra* transaction, const char* fldName)
	{
		bool rc = false;

		AutoCacheRequest handle(tdbb, irq_system_domain, IRQ_REQUESTS);
		FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
			R IN RDB$FIELDS WITH
			R.RDB$FIELD_NAME EQ fldName AND
			R.RDB$SYSTEM_FLAG EQ 1
		{
			rc = true;
		}
		END_FOR

		return rc;
	}
}


void METD_drop_charset(jrd_tra* transaction, const MetaName& metaName)
{
/**************************************
 *
 *  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.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	dsql_dbb* dbb = transaction->getDsqlAttachment();
	dsql_intlsym* charSet;

	if (dbb->dbb_charsets.get(metaName, charSet))
	{
		MET_dsql_cache_use(tdbb, SYM_intlsym_charset, metaName);
		charSet->intlsym_flags |= INTLSYM_dropped;
		dbb->dbb_charsets.remove(metaName);
		dbb->dbb_charsets_by_id.remove(charSet->intlsym_charset_id);
	}
}


void METD_drop_collation(jrd_tra* transaction, const MetaName& 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.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	dsql_dbb* dbb = transaction->getDsqlAttachment();

	dsql_intlsym* collation;

	if (dbb->dbb_collations.get(name, collation))
	{
		MET_dsql_cache_use(tdbb, SYM_intlsym_collation, name);
		collation->intlsym_flags |= INTLSYM_dropped;
		dbb->dbb_collations.remove(name);
	}
}


void METD_drop_function(jrd_tra* transaction, const QualifiedName& 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.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	dsql_dbb* dbb = transaction->getDsqlAttachment();

	dsql_udf* function;

	if (dbb->dbb_functions.get(name, function))
	{
		MET_dsql_cache_use(tdbb, SYM_udf, name.identifier, name.package);
		function->udf_flags |= UDF_dropped;
		dbb->dbb_functions.remove(name);
	}

}


void METD_drop_procedure(jrd_tra* transaction, const QualifiedName& 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.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	dsql_dbb* dbb = transaction->getDsqlAttachment();

	dsql_prc* procedure;

	if (dbb->dbb_procedures.get(name, procedure))
	{
		MET_dsql_cache_use(tdbb, SYM_procedure, name.identifier, name.package);
		procedure->prc_flags |= PRC_dropped;
		dbb->dbb_procedures.remove(name);
	}
}


void METD_drop_relation(jrd_tra* transaction, const MetaName& 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.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	dsql_dbb* dbb = transaction->getDsqlAttachment();

	dsql_rel* relation;

	if (dbb->dbb_relations.get(name, relation))
	{
		MET_dsql_cache_use(tdbb, SYM_relation, name);
		relation->rel_flags |= REL_dropped;
		dbb->dbb_relations.remove(name);
	}
}


dsql_intlsym* METD_get_collation(jrd_tra* transaction, const MetaName& 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.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();

	// Start by seeing if symbol is already defined

	dsql_intlsym* symbol;
	if (dbb->dbb_collations.get(name, symbol) && !(symbol->intlsym_flags & INTLSYM_dropped) &&
		symbol->intlsym_charset_id == charset_id)
	{
		if (MET_dsql_cache_use(tdbb, SYM_intlsym_collation, name))
			symbol->intlsym_flags |= INTLSYM_dropped;
		else
			return symbol;
	}

	// Now see if it is in the database

	symbol = NULL;

	AutoCacheRequest handle(tdbb, irq_collation, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
	X IN RDB$COLLATIONS
		CROSS Y IN RDB$CHARACTER_SETS OVER RDB$CHARACTER_SET_ID
		WITH X.RDB$COLLATION_NAME EQ name.c_str() AND
			 X.RDB$CHARACTER_SET_ID EQ charset_id;
	{
		symbol = FB_NEW_POOL(dbb->dbb_pool) dsql_intlsym(dbb->dbb_pool);
		symbol->intlsym_name = name;
		symbol->intlsym_flags = 0;
		symbol->intlsym_charset_id = X.RDB$CHARACTER_SET_ID;
		symbol->intlsym_collate_id = X.RDB$COLLATION_ID;
		symbol->intlsym_ttype =
			INTL_CS_COLL_TO_TTYPE(symbol->intlsym_charset_id, symbol->intlsym_collate_id);
		symbol->intlsym_bytes_per_char =
			(Y.RDB$BYTES_PER_CHARACTER.NULL) ? 1 : (Y.RDB$BYTES_PER_CHARACTER);
	}
	END_FOR

	if (!symbol)
		return NULL;

	dbb->dbb_collations.put(name, symbol);
	MET_dsql_cache_use(tdbb, SYM_intlsym_collation, name);

	return symbol;
}


USHORT METD_get_col_default(jrd_tra* transaction, 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(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();
	bid* blob_id;

	USHORT result = 0;
	blb* blob_handle = 0;

	*has_default = false;

	AutoCacheRequest handle(tdbb, irq_col_default, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE 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 (!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, 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 = blob_handle->BLB_get_segment(tdbb, 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->getFragmentSize())
					status_exception::raise(Arg::Gds(isc_segment));
				else
					continue;
			}

			try
			{
				ThreadStatusGuard status_vector(tdbb);

				blob_handle->BLB_close(tdbb);
				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 (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

	return result;
}


dsql_intlsym* METD_get_charset(jrd_tra* transaction, 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.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();

	validateTransaction(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();
	MetaName metaName(name, length);

	// Start by seeing if symbol is already defined

	dsql_intlsym* symbol;
	if (dbb->dbb_charsets.get(metaName, symbol) && !(symbol->intlsym_flags & INTLSYM_dropped))
	{
		if (MET_dsql_cache_use(tdbb, SYM_intlsym_charset, metaName))
			symbol->intlsym_flags |= INTLSYM_dropped;
		else
			return symbol;
	}

	// Now see if it is in the database

	symbol = NULL;

	AutoCacheRequest handle(tdbb, irq_charset, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE 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;
	{
		symbol = FB_NEW_POOL(dbb->dbb_pool) dsql_intlsym(dbb->dbb_pool);
		symbol->intlsym_name = metaName;
		symbol->intlsym_flags = 0;
		symbol->intlsym_charset_id = X.RDB$CHARACTER_SET_ID;
		symbol->intlsym_collate_id = X.RDB$COLLATION_ID;
		symbol->intlsym_ttype =
			INTL_CS_COLL_TO_TTYPE(symbol->intlsym_charset_id, symbol->intlsym_collate_id);
		symbol->intlsym_bytes_per_char =
			(Y.RDB$BYTES_PER_CHARACTER.NULL) ? 1 : (Y.RDB$BYTES_PER_CHARACTER);
	}
	END_FOR

	if (!symbol)
		return NULL;

	dbb->dbb_charsets.put(metaName, symbol);
	dbb->dbb_charsets_by_id.put(symbol->intlsym_charset_id, symbol);
	MET_dsql_cache_use(tdbb, SYM_intlsym_charset, metaName);

	return symbol;
}


USHORT METD_get_charset_bpc(jrd_tra* transaction, 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 = transaction->getDsqlAttachment();

	if (charset_id == CS_dynamic)
		charset_id = tdbb->getCharSet();

	dsql_intlsym* symbol = NULL;
	if (!dbb->dbb_charsets_by_id.get(charset_id, symbol))
	{
		const MetaName cs_name = METD_get_charset_name(transaction, charset_id);
		symbol = METD_get_charset(transaction, cs_name.length(), cs_name.c_str());
	}

	fb_assert(symbol);

	return symbol ? symbol->intlsym_bytes_per_char : 0;
}


MetaName METD_get_charset_name(jrd_tra* transaction, 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();

	validateTransaction(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();

    if (charset_id == CS_dynamic)
		charset_id = tdbb->getCharSet();

	dsql_intlsym* sym = NULL;
	if (dbb->dbb_charsets_by_id.get(charset_id, sym))
		return sym->intlsym_name;

	MetaName name;

	AutoCacheRequest handle(tdbb, irq_cs_name, IRQ_REQUESTS);

	FOR (REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
		Y IN RDB$CHARACTER_SETS
		WITH Y.RDB$CHARACTER_SET_ID EQ charset_id
	{
		name = Y.RDB$CHARACTER_SET_NAME;
	}
	END_FOR

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

	return name;
}


MetaName METD_get_default_charset(jrd_tra* transaction)
{
/**************************************
 *
 *  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();

	validateTransaction(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();
	if (dbb->dbb_no_charset)
		return NULL;

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

	// Now see if it is in the database

	AutoCacheRequest handle(tdbb, irq_default_cs, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
		FIRST 1 DBB IN RDB$DATABASE
		WITH DBB.RDB$CHARACTER_SET_NAME NOT MISSING;
	{
		// Terminate ASCIIZ string on first trailing blank
		fb_utils::exact_name(DBB.RDB$CHARACTER_SET_NAME);
		dbb->dbb_dfl_charset = DBB.RDB$CHARACTER_SET_NAME;
	}
	END_FOR

	if (dbb->dbb_dfl_charset.isEmpty())
		dbb->dbb_no_charset = true;

	return dbb->dbb_dfl_charset;
}


bool METD_get_domain(jrd_tra* transaction, TypeClause* field, const MetaName& 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(transaction);

	bool found = false;

	AutoCacheRequest handle(tdbb, irq_domain, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
		FLX IN RDB$FIELDS WITH FLX.RDB$FIELD_NAME EQ name.c_str()
	{
		found = true;
		field->length = FLX.RDB$FIELD_LENGTH;
		field->scale = FLX.RDB$FIELD_SCALE;
		field->subType = FLX.RDB$FIELD_SUB_TYPE;
		field->dimensions = FLX.RDB$DIMENSIONS.NULL ? 0 : FLX.RDB$DIMENSIONS;

		field->charSetId = Nullable<SSHORT>::empty();
		if (!FLX.RDB$CHARACTER_SET_ID.NULL)
			field->charSetId = FLX.RDB$CHARACTER_SET_ID;
		field->collationId = 0;
		if (!FLX.RDB$COLLATION_ID.NULL)
			field->collationId = FLX.RDB$COLLATION_ID;
		field->charLength = 0;
		if (!FLX.RDB$CHARACTER_LENGTH.NULL)
			field->charLength = FLX.RDB$CHARACTER_LENGTH;

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

		if (FLX.RDB$NULL_FLAG.NULL || !FLX.RDB$NULL_FLAG)
			field->flags |= FLD_nullable;

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

		convert_dtype(field, FLX.RDB$FIELD_TYPE);

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

	return found;
}


USHORT METD_get_domain_default(jrd_tra* transaction, const MetaName& 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(transaction);

	*has_default = false;

	dsql_dbb* dbb = transaction->getDsqlAttachment();
	USHORT result = 0;

	AutoCacheRequest handle(tdbb, irq_domain_2, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
		FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ domain_name.c_str()
	{
		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, 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 = blob_handle->BLB_get_segment(tdbb, 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->getFragmentSize())
					status_exception::raise(Arg::Gds(isc_segment));
				else
					continue;
			}

			try
			{
				ThreadStatusGuard status_vector(tdbb);

				blob_handle->BLB_close(tdbb);
				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 (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

	return result;
}


dsql_udf* METD_get_function(jrd_tra* transaction, DsqlCompilerScratch* dsqlScratch,
	const QualifiedName& 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();

	validateTransaction(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();
	QualifiedName metaName(name);

	bool maybeUnqualified = dsqlScratch->package.hasData() && metaName.package.isEmpty();
	if (maybeUnqualified)
		metaName.package = dsqlScratch->package;

	// Start by seeing if symbol is already defined

	dsql_udf* userFunc = NULL;
	if (dbb->dbb_functions.get(metaName, userFunc))
	{
		if (userFunc->udf_private && metaName.package != dsqlScratch->package)
		{
			status_exception::raise(Arg::Gds(isc_private_function) <<
				Arg::Str(metaName.identifier) << Arg::Str(metaName.package));
		}

		if (MET_dsql_cache_use(tdbb, SYM_udf, metaName.identifier, metaName.package))
			userFunc->udf_flags |= UDF_dropped;
	}

	if (userFunc && (userFunc->udf_flags & UDF_dropped))
		userFunc = NULL;

	if (userFunc)
		return userFunc;

	// Now see if it is in the database

	USHORT return_arg = 0;

	while (!userFunc)
	{
		AutoCacheRequest handle1(tdbb, irq_function, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE transaction)
			X IN RDB$FUNCTIONS WITH
			X.RDB$FUNCTION_NAME EQ metaName.identifier.c_str() AND
			X.RDB$PACKAGE_NAME EQUIV NULLIF(metaName.package.c_str(), '')
		{
			userFunc = FB_NEW_POOL(dbb->dbb_pool) dsql_udf(dbb->dbb_pool);
			userFunc->udf_name = metaName;
			userFunc->udf_private = !X.RDB$PRIVATE_FLAG.NULL && X.RDB$PRIVATE_FLAG != 0;

			return_arg = X.RDB$RETURN_ARGUMENT;
		}
		END_FOR

		if (!userFunc)
		{
			if (maybeUnqualified)
			{
				maybeUnqualified = false;
				metaName.package = "";
			}
			else
				return NULL;
		}
	}

	SSHORT defaults = 0;

	AutoCacheRequest handle2(tdbb, irq_func_return, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE transaction)
		X IN RDB$FUNCTION_ARGUMENTS WITH
		X.RDB$FUNCTION_NAME EQ metaName.identifier.c_str() AND
		X.RDB$PACKAGE_NAME EQUIV NULLIF(metaName.package.c_str(), '')
		SORTED BY X.RDB$ARGUMENT_POSITION
	{
		if (!X.RDB$FIELD_SOURCE.NULL)
		{
			AutoCacheRequest handle3(tdbb, irq_func_ret_fld, IRQ_REQUESTS);

			FOR(REQUEST_HANDLE handle3 TRANSACTION_HANDLE transaction)
				F IN RDB$FIELDS WITH
				F.RDB$FIELD_NAME EQ X.RDB$FIELD_SOURCE
			{
				if (X.RDB$ARGUMENT_POSITION == return_arg)
				{
					userFunc->udf_dtype = (F.RDB$FIELD_TYPE != blr_blob) ?
						gds_cvt_blr_dtype[F.RDB$FIELD_TYPE] : dtype_blob;
					userFunc->udf_scale = F.RDB$FIELD_SCALE;

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

					if (!F.RDB$CHARACTER_SET_ID.NULL) {
						userFunc->udf_character_set_id = F.RDB$CHARACTER_SET_ID;
					}

					if (!X.RDB$ARGUMENT_MECHANISM.NULL && X.RDB$ARGUMENT_MECHANISM == prm_mech_type_of &&
						!X.RDB$FIELD_NAME.NULL && X.RDB$FIELD_NAME[0] &&
						!X.RDB$RELATION_NAME.NULL && X.RDB$RELATION_NAME[0])
					{
						// type of column used in declaration
						if (isSystemRelation(tdbb, transaction, X.RDB$RELATION_NAME))
							userFunc->udf_flags |= UDF_sys_based;
					}
					else if (!X.RDB$FIELD_SOURCE.NULL && X.RDB$FIELD_SOURCE[0])
					{
						// domain used in declaration
						if (isSystemDomain(tdbb, transaction, X.RDB$FIELD_SOURCE))
							userFunc->udf_flags |= UDF_sys_based;
					}
				}
				else
				{
					DSC d;
					d.dsc_dtype = (F.RDB$FIELD_TYPE != blr_blob) ?
						gds_cvt_blr_dtype[F.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 = F.RDB$FIELD_SCALE;
					if (!F.RDB$FIELD_SUB_TYPE.NULL) {
						d.dsc_sub_type = F.RDB$FIELD_SUB_TYPE;
					}
					else {
						d.dsc_sub_type = 0;
					}
					d.dsc_length = F.RDB$FIELD_LENGTH;
					if (d.dsc_dtype == dtype_varying) {
						d.dsc_length += sizeof(USHORT);
					}
					d.dsc_address = NULL;

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

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

					if (!X.RDB$DEFAULT_VALUE.NULL ||
						(fb_utils::implicit_domain(F.RDB$FIELD_NAME) && !F.RDB$DEFAULT_VALUE.NULL))
					{
						defaults++;
					}

					userFunc->udf_arguments.add(d);
					userFunc->udf_fld_system_arguments.add(
						isSystemDomain(tdbb, transaction, X.RDB$FIELD_SOURCE));
				}
			}
			END_FOR
		}
		else
		{
			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;
				}

				if (!X.RDB$DEFAULT_VALUE.NULL)
				{
					defaults++;
				}

				userFunc->udf_arguments.add(d);
				userFunc->udf_fld_system_arguments.add(false);
			}
		}
	}
	END_FOR

	userFunc->udf_def_count = defaults;

	// 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);

	dbb->dbb_functions.put(userFunc->udf_name, userFunc);

	if (userFunc->udf_private && metaName.package != dsqlScratch->package)
	{
		status_exception::raise(Arg::Gds(isc_private_function) <<
			Arg::Str(metaName.identifier) << Arg::Str(metaName.package));
	}

	MET_dsql_cache_use(tdbb, SYM_udf, userFunc->udf_name.identifier, userFunc->udf_name.package);

	return userFunc;
}


void METD_get_primary_key(jrd_tra* transaction, const MetaName& relationName,
	Array<NestConst<FieldNode> >& fields)
{
/**************************************
 *
 *  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();
	MemoryPool& pool = *tdbb->getDefaultPool();

	validateTransaction(transaction);

	AutoCacheRequest handle(tdbb, irq_primary_key, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE 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 relationName.c_str()
		AND Z.RDB$CONSTRAINT_TYPE EQ "PRIMARY KEY"
		SORTED BY Y.RDB$FIELD_POSITION
	{
		FieldNode* fieldNode = FB_NEW_POOL(pool) FieldNode(pool);
		fieldNode->dsqlName = Y.RDB$FIELD_NAME;
		fields.add(fieldNode);
	}
	END_FOR
}


dsql_prc* METD_get_procedure(jrd_tra* transaction, DsqlCompilerScratch* dsqlScratch,
	const QualifiedName& 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();

	validateTransaction(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();

	// ASF: I've removed the code where we verify if the procedure being looked up is the one being
	// defined (dsqlScratch->procedure). This code is totally incorrect, not considering
	// transactions and savepoints, hence being incompatible with packages).
	// Example (with autocommit off):
	//
	// SQL> create procedure p1 as begin end!
	// SQL> create procedure p2 as begin execute procedure p1; end!
	// SQL> rollback!
	// SQL> execute procedure p2!
	// Statement failed, SQLSTATE = 42000
	// Dynamic SQL Error
	// -SQL error code = -204
	// -Procedure unknown
	// -P2
	// SQL> execute procedure p1!
	// Statement failed, SQLSTATE = 42000
	// invalid request BLR at offset 5
	// -procedure P1 is not defined
	//
	// The side effect is that this occur in more cases now:
	//
	// SQL> create procedure p as begin execute procedure p; execute procedure p2; end!
	// Statement failed, SQLSTATE = 42000
	// Dynamic SQL Error
	// -SQL error code = -204
	// -Procedure unknown
	// -P2
	// SQL> execute procedure p!
	// Statement failed, SQLSTATE = 42000
	// invalid request BLR at offset 4
	// -procedure P is not defined
	//
	// I hope for a solution, involving savepoint logic.

	QualifiedName metaName(name);

	bool maybeUnqualified = dsqlScratch->package.hasData() && metaName.package.isEmpty();
	if (maybeUnqualified)
		metaName.package = dsqlScratch->package;

	// Start by seeing if symbol is already defined

	dsql_prc* procedure = NULL;
	if (dbb->dbb_procedures.get(metaName, procedure))
	{
		if (procedure->prc_private && metaName.package != dsqlScratch->package)
		{
			status_exception::raise(Arg::Gds(isc_private_procedure) <<
				Arg::Str(metaName.identifier) << Arg::Str(metaName.package));
		}

		if (MET_dsql_cache_use(tdbb, SYM_procedure, metaName.identifier, metaName.package))
			procedure->prc_flags |= PRC_dropped;
	}

	if (procedure && (procedure->prc_flags & PRC_dropped))
		procedure = NULL;

	if (procedure)
		return procedure;

	// now see if it is in the database

	while (!procedure)
	{
		AutoCacheRequest handle1(tdbb, irq_procedure, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE transaction)
			X IN RDB$PROCEDURES
			WITH X.RDB$PROCEDURE_NAME EQ metaName.identifier.c_str() AND
				 X.RDB$PACKAGE_NAME EQUIV NULLIF(metaName.package.c_str(), '')
		{
			fb_utils::exact_name(X.RDB$OWNER_NAME);

			procedure = FB_NEW_POOL(dbb->dbb_pool) dsql_prc(dbb->dbb_pool);
			procedure->prc_id = X.RDB$PROCEDURE_ID;
			procedure->prc_name = metaName;
			procedure->prc_owner = X.RDB$OWNER_NAME;
			procedure->prc_private = !X.RDB$PRIVATE_FLAG.NULL && X.RDB$PRIVATE_FLAG != 0;
		}
		END_FOR

		if (!procedure)
		{
			if (maybeUnqualified)
			{
				maybeUnqualified = false;
				metaName.package = "";
			}
			else
				return NULL;
		}
	}

	// Lookup parameter stuff

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

		SSHORT count = 0, defaults = 0;

		AutoCacheRequest handle2(tdbb, irq_parameters, IRQ_REQUESTS);

		FOR (REQUEST_HANDLE handle2 TRANSACTION_HANDLE 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 metaName.identifier.c_str() AND
				 PR.RDB$PARAMETER_TYPE = type AND
				 PR.RDB$PACKAGE_NAME EQUIV NULLIF(metaName.package.c_str(), '')
			SORTED BY DESCENDING PR.RDB$PARAMETER_NUMBER
		{
			const SSHORT pr_collation_id_null = PR.RDB$COLLATION_ID.NULL;
			const SSHORT pr_collation_id = PR.RDB$COLLATION_ID;

			const SSHORT pr_default_value_null = PR.RDB$DEFAULT_VALUE.NULL;

			const SSHORT pr_null_flag_null = PR.RDB$NULL_FLAG.NULL;
			const SSHORT pr_null_flag = PR.RDB$NULL_FLAG;

			const bool pr_type_of =
				(!PR.RDB$PARAMETER_MECHANISM.NULL && PR.RDB$PARAMETER_MECHANISM == prm_mech_type_of);

			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_POOL(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->fieldSource = PR.RDB$FIELD_SOURCE;

			parameter->fld_id = PR.RDB$PARAMETER_NUMBER;
			parameter->length = FLD.RDB$FIELD_LENGTH;
			parameter->scale = FLD.RDB$FIELD_SCALE;
			parameter->subType = FLD.RDB$FIELD_SUB_TYPE;
			parameter->fld_procedure = procedure;

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

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

			convert_dtype(parameter, FLD.RDB$FIELD_TYPE);

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

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

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

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

			if (parameter->typeOfTable.hasData())
			{
				if (isSystemRelation(tdbb, transaction, parameter->typeOfTable.c_str()))
					parameter->flags |= FLD_system;
			}
			else if (parameter->typeOfName.hasData())
			{
				if (isSystemDomain(tdbb, transaction, parameter->typeOfName.c_str()))
					parameter->flags |= FLD_system;
			}
			else if (parameter->fieldSource.hasData())
			{
				if (isSystemDomain(tdbb, transaction, parameter->fieldSource.c_str()))
					parameter->flags |= FLD_system;
			}

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

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

	dbb->dbb_procedures.put(procedure->prc_name, procedure);

	if (procedure->prc_private && metaName.package != dsqlScratch->package)
	{
		status_exception::raise(Arg::Gds(isc_private_procedure) <<
			Arg::Str(metaName.identifier) << Arg::Str(metaName.package));
	}

	MET_dsql_cache_use(tdbb, SYM_procedure, procedure->prc_name.identifier,
		procedure->prc_name.package);

	return procedure;
}


dsql_rel* METD_get_relation(jrd_tra* transaction, DsqlCompilerScratch* dsqlScratch,
	const MetaName& 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();

	validateTransaction(transaction);

	dsql_dbb* dbb = transaction->getDsqlAttachment();

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

	dsql_rel* temp = dsqlScratch->relation;
	if (temp != NULL && temp->rel_name == name)
		return temp;

	// Start by seeing if symbol is already defined

	if (dbb->dbb_relations.get(name, temp) && !(temp->rel_flags & REL_dropped))
	{
		if (MET_dsql_cache_use(tdbb, SYM_relation, name))
			temp->rel_flags |= REL_dropped;
		else
			return temp;
	}

	// 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

	bool permanent = true;

	AutoCacheRequest handle1(tdbb, irq_rel_ids, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE transaction)
		REL IN RDB$RELATIONS
		CROSS RFR IN RDB$RELATION_FIELDS OVER RDB$RELATION_NAME
		WITH REL.RDB$RELATION_NAME EQ name.c_str()
		AND (REL.RDB$RELATION_ID MISSING OR RFR.RDB$FIELD_ID MISSING)
	{
		permanent = false;
	}
	END_FOR

	// Now see if it is in the database

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

	dsql_rel* relation = NULL;

	AutoCacheRequest handle2(tdbb, irq_relation, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE transaction)
		X IN RDB$RELATIONS WITH X.RDB$RELATION_NAME EQ name.c_str()
	{
		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(pool) dsql_rel(pool);
			relation->rel_id = X.RDB$RELATION_ID;
		}
		else if (!DDL_ids(dsqlScratch))
			relation = FB_NEW_POOL(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 (!relation)
		return NULL;

	// Lookup field stuff

	dsql_fld** ptr = &relation->rel_fields;

	AutoCacheRequest handle3(tdbb, irq_fields, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle3 TRANSACTION_HANDLE 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.c_str()
		SORTED BY RFR.RDB$FIELD_POSITION
	{
		// 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(pool) dsql_fld(pool);
			field->fld_id = RFR.RDB$FIELD_ID;
		}
		else if (!DDL_ids(dsqlScratch))
			field = FB_NEW_POOL(pool) dsql_fld(pool);

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

			// get field information

			field->fld_name = RFR.RDB$FIELD_NAME;
			field->fieldSource = RFR.RDB$FIELD_SOURCE;
			field->length = FLX.RDB$FIELD_LENGTH;
			field->scale = FLX.RDB$FIELD_SCALE;
			field->subType = FLX.RDB$FIELD_SUB_TYPE;
			field->fld_relation = relation;

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

			convert_dtype(field, FLX.RDB$FIELD_TYPE);

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

			if (!FLX.RDB$DIMENSIONS.NULL && FLX.RDB$DIMENSIONS)
			{
				field->elementDtype = field->dtype;
				field->elementLength = field->length;
				field->dtype = dtype_array;
				field->length = sizeof(ISC_QUAD);
				field->dimensions = FLX.RDB$DIMENSIONS;
			}

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

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

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

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

	if (dbb->dbb_relations.get(name, temp) && !(temp->rel_flags & REL_dropped))
	{
		free_relation(relation);
		return temp;
	}

	// Add relation to the list

	if (permanent)
	{
		dbb->dbb_relations.put(relation->rel_name, relation);
		MET_dsql_cache_use(tdbb, SYM_relation, relation->rel_name);
	}
	else
		relation->rel_flags |= REL_new_relation;

	return relation;
}


bool METD_get_type(jrd_tra* transaction, const MetaName& 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(transaction);

	bool found = false;

	AutoCacheRequest handle(tdbb, irq_type, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
		X IN RDB$TYPES WITH
		X.RDB$FIELD_NAME EQ field AND X.RDB$TYPE_NAME EQ name.c_str();
	{
		found = true;
		*value = X.RDB$TYPE;
	}
	END_FOR

	return found;
}


dsql_rel* METD_get_view_base(jrd_tra* transaction, DsqlCompilerScratch* dsqlScratch,
	const char* view_name, 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(transaction);

	dsql_rel* relation = NULL;

	bool first = true;
	bool cont = true;
	MetaNamePairMap previousAux;

	fields.clear();

	while (cont)
	{
		AutoCacheRequest handle1(tdbb, irq_view_base, IRQ_REQUESTS);

		FOR(REQUEST_HANDLE handle1 TRANSACTION_HANDLE transaction)
			X IN RDB$VIEW_RELATIONS
			WITH X.RDB$VIEW_NAME EQ view_name
		{
			// return NULL if there is more than one context
			if (X.RDB$VIEW_CONTEXT != 1 || X.RDB$CONTEXT_TYPE == VCT_PROCEDURE)
			{
				relation = NULL;
				cont = false;
				break;
			}

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

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

			Array<MetaName> ambiguities;
			MetaNamePairMap currentAux;

			if (!relation)
			{
				cont = false;
				break;
			}

			AutoCacheRequest handle2(tdbb, irq_view_base_flds, IRQ_REQUESTS);

			FOR(REQUEST_HANDLE handle2 TRANSACTION_HANDLE transaction)
				RFL IN RDB$RELATION_FIELDS
				WITH RFL.RDB$RELATION_NAME EQ X.RDB$VIEW_NAME
			{
				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

			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;
				break;
			}

			first = false;
		}
		END_FOR
	}

	if (!relation)
		fields.clear();

	return relation;
}


dsql_rel* METD_get_view_relation(jrd_tra* transaction, DsqlCompilerScratch* dsqlScratch,
	const char* view_name, const char* relation_or_alias)
{
/**************************************
 *
 *  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(transaction);

	dsql_rel* relation = NULL;

	AutoCacheRequest handle(tdbb, irq_view, IRQ_REQUESTS);

	FOR(REQUEST_HANDLE handle TRANSACTION_HANDLE transaction)
		X IN RDB$VIEW_RELATIONS WITH X.RDB$VIEW_NAME EQ view_name
	{
		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(transaction, dsqlScratch, X.RDB$RELATION_NAME);
			return relation;
		}

		relation = METD_get_view_relation(transaction, dsqlScratch, X.RDB$RELATION_NAME,
			relation_or_alias);

		if (relation)
			return relation;
	}
	END_FOR

	return NULL;
}


static void convert_dtype(TypeClause* 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->dtype = dtype_text;
		break;
	case blr_varying:
		field->dtype = dtype_varying;
		field->length += sizeof(USHORT);
		break;
	case blr_blob:
		field->dtype = dtype_blob;
		field->length = type_lengths[field->dtype];
		break;
	default:
		field->dtype = gds_cvt_blr_dtype[field_type];
		field->length = type_lengths[field->dtype];

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


#ifdef NOT_USED_OR_REPLACED
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;
}
#endif	// NOT_USED_OR_REPLACED


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;
}
