/*
 * 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): ______________________________________.
 * Adriano dos Santos Fernandes - refactored from pass1.cpp, ddl.cpp, dyn*.epp
 */

#include "firebird.h"
#include "dyn_consts.h"
#include "../dsql/DdlNodes.h"
#include "../dsql/BoolNodes.h"
#include "../dsql/ExprNodes.h"
#include "../dsql/StmtNodes.h"
#include "firebird/impl/blr.h"
#include "../jrd/btr.h"
#include "../jrd/dyn.h"
#include "../jrd/flags.h"
#include "../jrd/intl.h"
#include "../jrd/jrd.h"
#include "../common/msg_encode.h"
#include "../jrd/obj.h"
#include "../jrd/ods.h"
#include "../jrd/tra.h"
#include "../common/os/path_utils.h"
#include "../jrd/CryptoManager.h"
#include "../jrd/IntlManager.h"
#include "../jrd/PreparedStatement.h"
#include "../jrd/ResultSet.h"
#include "../jrd/UserManagement.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/dyn_ut_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/intl_proto.h"
#include "../common/isc_f_proto.h"
#include "../jrd/lck_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/scl_proto.h"
#include "../jrd/vio_proto.h"
#include "../dsql/ddl_proto.h"
#include "../dsql/errd_proto.h"
#include "../dsql/gen_proto.h"
#include "../dsql/make_proto.h"
#include "../dsql/metd_proto.h"
#include "../dsql/pass1_proto.h"
#include "../utilities/gsec/gsec.h"
#include "../common/dsc_proto.h"
#include "../common/StatusArg.h"
#include "../auth/SecureRemotePassword/Message.h"
#include "../jrd/Mapping.h"
#include "../jrd/extds/ExtDS.h"

namespace Jrd {

using namespace Firebird;

static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction,
	const MetaName&	childRelName, const MetaName& masterIndexName);
static void checkSpTrigDependency(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName);
static void checkViewDependency(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName);
static void clearPermanentField(dsql_rel* relation, bool permanent);
static void defineComputed(DsqlCompilerScratch* dsqlScratch, RelationSourceNode* relation,
	dsql_fld* field, ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value);
static void deleteKeyConstraint(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& constraintName, const MetaName& indexName);
static void defineFile(thread_db* tdbb, jrd_tra* transaction, SLONG shadowNumber, bool manualShadow,
	bool conditionalShadow, SLONG& dbAlloc,
	const PathName& name, SLONG start, SLONG length);
static bool fieldExists(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName,
	const MetaName& fieldName);
static bool isItSqlRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& inputName,
	MetaName& outputName);
static int getGrantorOption(thread_db* tdbb, jrd_tra* transaction, const MetaName& grantor,
	int grantorType, const MetaName& roleName);
static MetaName getIndexRelationName(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& indexName, bool& systemIndex);
static const char* getRelationScopeName(const rel_t type);
static void makeRelationScopeName(string& to, const MetaName& name, const rel_t type);
static void checkRelationType(const rel_t type, const MetaName& name);
static void checkFkPairTypes(const rel_t masterType, const MetaName& masterName,
	const rel_t childType, const MetaName& childName);
static void modifyLocalFieldPosition(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName, USHORT newPosition);
static rel_t relationType(SSHORT relationTypeNull, SSHORT relationType);
static void saveField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& fieldName);
static void saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	const MetaName& relationName, bool view, bool creating);
static void updateRdbFields(const TypeClause* type,
	SSHORT& fieldType,
	SSHORT& fieldLength,
	SSHORT& fieldSubTypeNull, SSHORT& fieldSubType,
	SSHORT& fieldScaleNull, SSHORT& fieldScale,
	SSHORT& characterSetIdNull, SSHORT& characterSetId,
	SSHORT& characterLengthNull, SSHORT& characterLength,
	SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision,
	SSHORT& collationIdNull, SSHORT& collationId,
	SSHORT& segmentLengthNull, SSHORT& segmentLength);

static const char* const CHECK_CONSTRAINT_EXCEPTION = "check_constraint";

DATABASE DB = STATIC "ODS.RDB";


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

void ExecInSecurityDb::executeInSecurityDb(jrd_tra* localTransaction)
{
	LocalStatus st;
	CheckStatusWrapper statusWrapper(&st);

	SecDbContext* secDbContext = localTransaction->getSecDbContext();
	if (!secDbContext)
	{
		Attachment* lAtt = localTransaction->getAttachment();
		fb_assert(lAtt && lAtt->att_database && lAtt->att_database->dbb_config); // paranoid check
		const char* secDb = lAtt->att_database->dbb_config->getSecurityDatabase();
		ClumpletWriter dpb(ClumpletWriter::WideTagged, MAX_DPB_SIZE, isc_dpb_version2);
		if (lAtt->att_user)
			lAtt->att_user->populateDpb(dpb, true);
		IAttachment* att = DispatcherPtr()->attachDatabase(&statusWrapper, secDb,
			dpb.getBufferLength(), dpb.getBuffer());
		check(&statusWrapper);

		ITransaction* tra = att->startTransaction(&statusWrapper, 0, NULL);
		check(&statusWrapper);

		secDbContext = localTransaction->setSecDbContext(att, tra);
	}

	// run all statements under savepoint control
	string savePoint;
	savePoint.printf("ExecInSecurityDb%d", secDbContext->savePoint++);
	secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, ("SAVEPOINT " + savePoint).c_str(),
		SQL_DIALECT_V6, NULL, NULL, NULL, NULL);
	check(&statusWrapper);

	try
	{
		runInSecurityDb(secDbContext);

		secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0,
			("RELEASE SAVEPOINT " + savePoint).c_str(),
			SQL_DIALECT_V6, NULL, NULL, NULL, NULL);
		savePoint.erase();
		check(&statusWrapper);
	}
	catch (const Exception&)
	{
		if (savePoint.hasData())
		{
			LocalStatus tmp;
			CheckStatusWrapper tmpCheckStatusWrapper(&tmp);
			secDbContext->att->execute(&tmpCheckStatusWrapper, secDbContext->tra, 0,
				("ROLLBACK TO SAVEPOINT " + savePoint).c_str(),
				SQL_DIALECT_V6, NULL, NULL, NULL, NULL);
		}

		throw;
	}
}


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


// Check temporary table reference rules between given child relation and master
// relation (owner of given PK/UK index).
static void checkForeignKeyTempScope(thread_db* tdbb, jrd_tra* transaction,
	const MetaName&	childRelName, const MetaName& masterIndexName)
{
	AutoCacheRequest request(tdbb, drq_l_rel_info, DYN_REQUESTS);
	MetaName masterRelName;
	rel_t masterType, childType;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RLC_M IN RDB$RELATION_CONSTRAINTS CROSS
		REL_C IN RDB$RELATIONS CROSS
		REL_M IN RDB$RELATIONS
		WITH (RLC_M.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT OR
			  RLC_M.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY) AND
			 RLC_M.RDB$INDEX_NAME EQ masterIndexName.c_str() AND
			 REL_C.RDB$RELATION_NAME EQ childRelName.c_str() AND
			 REL_M.RDB$RELATION_NAME EQ RLC_M.RDB$RELATION_NAME
	{
		fb_assert(masterRelName.isEmpty());

		masterRelName = REL_M.RDB$RELATION_NAME;
		masterType = relationType(REL_M.RDB$RELATION_TYPE.NULL, REL_M.RDB$RELATION_TYPE);
		childType = relationType(REL_C.RDB$RELATION_TYPE.NULL, REL_C.RDB$RELATION_TYPE);

	}
	END_FOR

	if (masterRelName.hasData())
	{
		checkRelationType(masterType, masterRelName);
		checkRelationType(childType, childRelName);
		checkFkPairTypes(masterType, masterRelName, childType, childRelName);
	}
}

// Check temporary table reference rules between just created child relation and all
// its master relations.
static void checkRelationTempScope(thread_db* tdbb, jrd_tra* transaction,
	const MetaName&	childRelName, const rel_t childType)
{
	if (childType != rel_persistent &&
		childType != rel_global_temp_preserve &&
		childType != rel_global_temp_delete)
	{
		return;
	}

	AutoCacheRequest request(tdbb, drq_l_rel_info2, DYN_REQUESTS);
	MetaName masterRelName;
	rel_t masterType;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RLC_C IN RDB$RELATION_CONSTRAINTS CROSS
		IND_C IN RDB$INDICES CROSS
		IND_M IN RDB$INDICES CROSS
		REL_M IN RDB$RELATIONS
		WITH RLC_C.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY AND
			 RLC_C.RDB$RELATION_NAME EQ childRelName.c_str() AND
			 IND_C.RDB$INDEX_NAME EQ RLC_C.RDB$INDEX_NAME AND
			 IND_M.RDB$INDEX_NAME EQ IND_C.RDB$FOREIGN_KEY AND
			 IND_M.RDB$RELATION_NAME EQ REL_M.RDB$RELATION_NAME
	{
		fb_assert(masterRelName.isEmpty());

		masterType = relationType(REL_M.RDB$RELATION_TYPE.NULL, REL_M.RDB$RELATION_TYPE);
		masterRelName = REL_M.RDB$RELATION_NAME;
	}
	END_FOR

	if (masterRelName.hasData())
	{
		checkRelationType(masterType, masterRelName);
		checkFkPairTypes(masterType, masterRelName, childType, childRelName);
	}
}

// Checks to see if the given field is referenced in a stored procedure or trigger.
// If the field is referenced, throw.
static void checkSpTrigDependency(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName)
{
	AutoRequest request;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIRST 1
		DEP IN RDB$DEPENDENCIES
		WITH DEP.RDB$DEPENDED_ON_NAME EQ relationName.c_str() AND
			 DEP.RDB$DEPENDED_ON_TYPE EQ obj_relation AND
			 DEP.RDB$FIELD_NAME EQ fieldName.c_str()
	{
		MetaName depName(DEP.RDB$DEPENDENT_NAME);

		// msg 206: Column %s from table %s is referenced in %s.
		status_exception::raise(
			Arg::PrivateDyn(206) << fieldName << relationName << depName);
	}
	END_FOR
}

// Checks to see if the given field is referenced in a view. If it is, throw.
static void checkViewDependency(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName)
{
	AutoRequest request;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIRST 1
		X IN RDB$RELATION_FIELDS CROSS
		Y IN RDB$RELATION_FIELDS CROSS
		Z IN RDB$VIEW_RELATIONS
		WITH X.RDB$RELATION_NAME EQ relationName.c_str() AND
			 X.RDB$FIELD_NAME EQ fieldName.c_str() AND
			 X.RDB$FIELD_NAME EQ Y.RDB$BASE_FIELD AND
			 X.RDB$FIELD_SOURCE EQ Y.RDB$FIELD_SOURCE AND
			 Y.RDB$RELATION_NAME EQ Z.RDB$VIEW_NAME AND
			 X.RDB$RELATION_NAME EQ Z.RDB$RELATION_NAME AND
			 Y.RDB$VIEW_CONTEXT EQ Z.RDB$VIEW_CONTEXT
	{
		MetaName viewName(Z.RDB$VIEW_NAME);

		// msg 206: Column %s from table %s is referenced in  %s.
		status_exception::raise(
			Arg::PrivateDyn(206) << fieldName << relationName << viewName);
	}
	END_FOR
}

// Removes temporary pool pointers from field, stored in permanent cache.
static void clearPermanentField(dsql_rel* relation, bool permanent)
{
	if (relation && relation->rel_fields && permanent)
	{
		relation->rel_fields->fld_procedure = NULL;
		relation->rel_fields->ranges = NULL;
		relation->rel_fields->charSet = NULL;
		relation->rel_fields->subTypeName = NULL;
		relation->rel_fields->fld_relation = relation;
	}
}

// Define a COMPUTED BY clause, for a field or an index.
void defineComputed(DsqlCompilerScratch* dsqlScratch, RelationSourceNode* relation, dsql_fld* field,
	ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value)
{
	// Get the table node and set up correct context.
	dsqlScratch->resetContextStack();

	// Save the size of the field if it is specified.
	dsc saveDesc;
	saveDesc.dsc_dtype = 0;
	bool saveCharSetIdSpecified;

	if (field && field->dtype)
	{
		fb_assert(field->dtype <= MAX_UCHAR);
		saveDesc.dsc_dtype = (UCHAR) field->dtype;
		saveDesc.dsc_length = field->length;
		fb_assert(field->scale <= MAX_SCHAR);
		saveDesc.dsc_scale = (SCHAR) field->scale;
		saveDesc.dsc_sub_type = field->subType;
		saveCharSetIdSpecified = field->charSetId.specified;

		field->dtype = 0;
		field->length = 0;
		field->scale = 0;
		field->subType = 0;
	}

	PASS1_make_context(dsqlScratch, relation);

	ValueExprNode* input = Node::doDsqlPass(dsqlScratch, clause->value);

	// Try to calculate size of the computed field. The calculated size
	// may be ignored, but it will catch self references.
	dsc desc;
	DsqlDescMaker::fromNode(dsqlScratch, &desc, input);

	// Generate the blr expression.

	dsqlScratch->getBlrData().clear();
	dsqlScratch->getDebugData().clear();
	dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

	GEN_expr(dsqlScratch, input);
	dsqlScratch->appendUChar(blr_eoc);

	if (saveDesc.dsc_dtype)
	{
		// Restore the field size/type overrides.
		field->dtype = saveDesc.dsc_dtype;
		field->length = saveDesc.dsc_length;
		field->scale = saveDesc.dsc_scale;

		if (field->dtype <= dtype_any_text)
		{
			field->charSetId = DSC_GET_CHARSET(&saveDesc);
			field->charSetId.specified = saveCharSetIdSpecified;
			field->collationId = DSC_GET_COLLATE(&saveDesc);
		}
		else
			field->subType = saveDesc.dsc_sub_type;
	}
	else if (field)
	{
		// Use size calculated.
		field->dtype = desc.dsc_dtype;
		field->length = desc.dsc_length;
		field->scale = desc.dsc_scale;

		if (field->dtype <= dtype_any_text)
		{
			field->charSetId = DSC_GET_CHARSET(&desc);
			field->collationId = DSC_GET_COLLATE(&desc);

			const USHORT adjust = field->dtype == dtype_varying ? sizeof(USHORT) : 0;
			const USHORT bpc = METD_get_charset_bpc(dsqlScratch->getTransaction(), field->charSetId.value);
			field->charLength = (field->length - adjust) / bpc;
		}
		else if (field->dtype == dtype_blob)
		{
			field->charSetId = desc.getCharSet();
			field->collationId = desc.getCollation();
			field->subType = desc.getBlobSubType();
		}
		else
			field->subType = desc.dsc_sub_type;
	}

	if (field)
		field->setExactPrecision();

	dsqlScratch->resetContextStack();

	// Generate the source text.
	source = clause->source;

	value.assign(dsqlScratch->getBlrData());
}

// Delete a record from RDB$RELATION_CONSTRAINTS based on a constraint name.
//
//      On deleting from RDB$RELATION_CONSTRAINTS, 2 system triggers fire:
//
//      (A) pre delete trigger: pre_delete_constraint, will:
//
//        1. delete a record first from RDB$REF_CONSTRAINTS where
//           RDB$REF_CONSTRAINTS.RDB$CONSTRAINT_NAME =
//                               RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME
//
//      (B) post delete trigger: post_delete_constraint will:
//
//        1. also delete a record from RDB$INDICES where
//           RDB$INDICES.RDB$INDEX_NAME =
//                               RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME
//
//        2. also delete a record from RDB$INDEX_SEGMENTS where
//           RDB$INDEX_SEGMENTS.RDB$INDEX_NAME =
//                               RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME
static void deleteKeyConstraint(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& constraintName, const MetaName& indexName)
{
	SET_TDBB(tdbb);

	AutoCacheRequest request(tdbb, drq_e_rel_const, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RC IN RDB$RELATION_CONSTRAINTS
		WITH RC.RDB$CONSTRAINT_NAME EQ constraintName.c_str() AND
			 RC.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY AND
			 RC.RDB$RELATION_NAME EQ relationName.c_str() AND
			 RC.RDB$INDEX_NAME EQ indexName.c_str()
	{
		found = true;
		ERASE RC;
	}
	END_FOR

	if (!found)
	{
		// msg 130: "CONSTRAINT %s does not exist."
		status_exception::raise(
			Arg::PrivateDyn(130) << constraintName);
	}
}

// Define a database or shadow file.
static void defineFile(thread_db* tdbb, jrd_tra* transaction, SLONG shadowNumber, bool manualShadow,
	bool conditionalShadow, SLONG& dbAlloc, const PathName& name, SLONG start, SLONG length)
{
	PathName expandedName = name;

	if (!ISC_expand_filename(expandedName, false))
		status_exception::raise(Arg::PrivateDyn(231));	// File name is invalid.

	if (tdbb->getDatabase()->dbb_filename == expandedName)
		status_exception::raise(Arg::PrivateDyn(166));

	AutoCacheRequest request(tdbb, drq_l_files, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIRST 1 X IN RDB$FILES
		WITH X.RDB$FILE_NAME EQ expandedName.c_str()
	{
		status_exception::raise(Arg::PrivateDyn(166));
	}
	END_FOR

	request.reset(tdbb, drq_s_files, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$FILES
	{
		expandedName.copyTo(X.RDB$FILE_NAME, sizeof(X.RDB$FILE_NAME));
		X.RDB$SHADOW_NUMBER = shadowNumber;
		X.RDB$FILE_FLAGS = (manualShadow ? FILE_manual : 0) |
			(conditionalShadow ? FILE_conditional : 0);

		dbAlloc = MAX(dbAlloc, start);
		X.RDB$FILE_START = dbAlloc;
		X.RDB$FILE_LENGTH = length;
		dbAlloc += length;
	}
	END_STORE
}

// Checks to see if the given field already exists in a relation.
static bool fieldExists(thread_db* tdbb, jrd_tra* transaction, const MetaName& relationName,
	const MetaName& fieldName)
{
	AutoRequest request;
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FLD IN RDB$RELATION_FIELDS
		WITH FLD.RDB$RELATION_NAME EQ relationName.c_str() AND
			 FLD.RDB$FIELD_NAME EQ fieldName.c_str()
	{
		found = true;
	}
	END_FOR

	return found;
}

// If inputName is found in RDB$ROLES, then returns true. Otherwise returns false.
static bool isItSqlRole(thread_db* tdbb, jrd_tra* transaction, const MetaName& inputName,
	MetaName& outputName)
{
	AutoCacheRequest request(tdbb, drq_get_role_nm, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$ROLES
		WITH X.RDB$ROLE_NAME EQ inputName.c_str()
	{
		outputName = X.RDB$OWNER_NAME;
		return true;
	}
	END_FOR

	return false;
}

// Make string with relation name and type of its temporary scope.
static void makeRelationScopeName(string& to, const MetaName& name, const rel_t type)
{
	const char* scope = getRelationScopeName(type);
	to.printf(scope, name.c_str());
}

// Get relation name of an index.
static MetaName getIndexRelationName(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& indexName, bool& systemIndex)
{
	systemIndex = false;

	AutoCacheRequest request(tdbb, drq_l_index_relname, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES
		WITH IDX.RDB$INDEX_NAME EQ indexName.c_str()
	{
		systemIndex = IDX.RDB$SYSTEM_FLAG == 1;
		return IDX.RDB$RELATION_NAME;
	}
	END_FOR

	// msg 48: "Index not found"
	status_exception::raise(Arg::PrivateDyn(48));
	return "";	// silence warning
}

// Get relation name of an trigger.
static MetaName getTriggerRelationName(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& triggerName)
{
	AutoCacheRequest request(tdbb, drq_l_trigger_relname, DYN_REQUESTS);

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$TRIGGERS
		WITH X.RDB$TRIGGER_NAME EQ triggerName.c_str()
	{
		return X.RDB$RELATION_NAME;
	}
	END_FOR

	return "";
}

// Get relation type name
static const char* getRelationScopeName(const rel_t type)
{
	switch(type)
	{
	case rel_global_temp_preserve:
		return REL_SCOPE_GTT_PRESERVE;
	case rel_global_temp_delete:
		return REL_SCOPE_GTT_DELETE;
	case rel_external:
		return REL_SCOPE_EXTERNAL;
	case rel_view:
		return REL_SCOPE_VIEW;
	case rel_virtual:
		return REL_SCOPE_VIRTUAL;
	}

	return REL_SCOPE_PERSISTENT;
}

// Check, can relation of given type be used in FK?
static void checkRelationType(const rel_t type, const MetaName& name)
{
	if (type == rel_persistent ||
		type == rel_global_temp_preserve ||
		type == rel_global_temp_delete)
	{
		return;
	}

	string scope;
	makeRelationScopeName(scope, name, type);
	(Arg::PrivateDyn(289) << scope).raise();
}

// Check, can a pair of relations be used in FK
static void checkFkPairTypes(const rel_t masterType, const MetaName& masterName,
	const rel_t childType, const MetaName& childName)
{
	if (masterType != childType &&
		!(masterType == rel_global_temp_preserve && childType == rel_global_temp_delete))
	{
		string master, child;
		makeRelationScopeName(master, masterName, masterType);
		makeRelationScopeName(child, childName, childType);
		// Msg 232 : "%s can't reference %s"
		status_exception::raise(Arg::PrivateDyn(232) << child << master);
	}
}


// Alters the position of a field with respect to the
// other fields in the relation.  This will only affect
// the order in which the fields will be returned when either
// viewing the relation or performing select * from the relation.
//
// The rules of engagement are as follows:
//      if new_position > original position
//         increase RDB$FIELD_POSITION for all fields with RDB$FIELD_POSITION
//         between the new_position and existing position of the field
//      then update the position of the field being altered.
//         just update the position
//
//      if new_position < original position
//         decrease RDB$FIELD_POSITION for all fields with RDB$FIELD_POSITION
//         between the new_position and existing position of the field
//      then update the position of the field being altered.
//
//      if new_position == original_position -- no_op
static void modifyLocalFieldPosition(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName, USHORT newPosition)
{
	USHORT existingPosition = 0;
	bool found = false;

	// Make sure that there are no duplicate field positions and no gaps in the position sequence.
	// (gaps are introduced when fields are removed)

	AutoRequest request;
	USHORT newPos = 0;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FLD IN RDB$RELATION_FIELDS
		WITH FLD.RDB$RELATION_NAME EQ relationName.c_str()
		SORTED BY ASCENDING FLD.RDB$FIELD_POSITION
	{
		if (FLD.RDB$FIELD_POSITION != newPos)
		{
			MODIFY FLD USING
				FLD.RDB$FIELD_POSITION = newPos;
			END_MODIFY
		}

		if (fieldName == FLD.RDB$FIELD_NAME)
		{
			found = true;
			existingPosition = newPos;
		}

		++newPos;
	}
	END_FOR

	if (!found)
	{
		// msg 176: "column %s does not exist in table/view %s"
		status_exception::raise(Arg::PrivateDyn(176) << fieldName << relationName);
	}

	// Find the position of the last field in the relation.
	SLONG maxPosition = SLONG(newPos) - 1;

	// If the existing position of the field is less than the new position of
	// the field, subtract 1 to move the fields to their new positions otherwise,
	// increase the value in RDB$FIELD_POSITION by one.

	const bool moveDown = (existingPosition < newPosition);

	// Retrieve the records for the fields which have a position between the
	// existing field position and the new field position.

	request.reset();

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FLD IN RDB$RELATION_FIELDS
		WITH FLD.RDB$RELATION_NAME EQ relationName.c_str() AND
				FLD.RDB$FIELD_POSITION >= MIN(newPosition, existingPosition) AND
				FLD.RDB$FIELD_POSITION <= MAX(newPosition, existingPosition)
	{
		MODIFY FLD USING
			// If the field is the one we want, change the position, otherwise
			// increase the value of RDB$FIELD_POSITION.
			if (fieldName == FLD.RDB$FIELD_NAME)
			{
				if (newPosition > maxPosition)
				{
					// This prevents gaps in the position sequence of the fields.
					FLD.RDB$FIELD_POSITION = maxPosition;
				}
				else
					FLD.RDB$FIELD_POSITION = newPosition;
			}
			else
			{
				if (moveDown)
					FLD.RDB$FIELD_POSITION = FLD.RDB$FIELD_POSITION - 1;
				else
					FLD.RDB$FIELD_POSITION = FLD.RDB$FIELD_POSITION + 1;
			}

			FLD.RDB$FIELD_POSITION.NULL = FALSE;
		END_MODIFY
	}
	END_FOR
}

// Convert RDB$RELATION_TYPE to rel_t type.
static rel_t relationType(SSHORT relationTypeNull, SSHORT relationType)
{
	return relationTypeNull ? rel_persistent : rel_t(relationType);
}

// Save the name of a field in the relation or view currently being defined. This is done to support
// definition of triggers which will depend on the metadata created in this statement.
static void saveField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, const MetaName& fieldName)
{
	dsql_rel* relation = dsqlScratch->relation;
	if (!relation)
		return;

	MemoryPool& p = relation->rel_flags & REL_new_relation ?
		*tdbb->getDefaultPool() : dsqlScratch->getAttachment()->dbb_pool;
	dsql_fld* field = FB_NEW_POOL(p) dsql_fld(p);
	field->fld_name = fieldName.c_str();
	field->fld_next = relation->rel_fields;
	relation->rel_fields = field;
}

// Save the name of the relation or view currently being defined. This is done to support definition
// of triggers which will depend on the metadata created in this statement.
static void saveRelation(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	const MetaName& relationName, bool view, bool creating)
{
	if (dsqlScratch->flags & DsqlCompilerScratch::FLAG_METADATA_SAVED)
		return;

	dsqlScratch->flags |= DsqlCompilerScratch::FLAG_METADATA_SAVED;

	dsql_rel* relation;

	if (!view && !creating)
		relation = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, relationName);
	else
	{
		MemoryPool& pool = *tdbb->getDefaultPool();
		relation = FB_NEW_POOL(pool) dsql_rel(pool);
		relation->rel_name = relationName;
		if (!view)
			relation->rel_flags = REL_creating;
	}

	dsqlScratch->relation = relation;
}

// Update RDB$FIELDS received by reference.
static void updateRdbFields(const TypeClause* type,
	SSHORT& fieldType,
	SSHORT& fieldLength,
	SSHORT& fieldSubTypeNull, SSHORT& fieldSubType,
	SSHORT& fieldScaleNull, SSHORT& fieldScale,
	SSHORT& characterSetIdNull, SSHORT& characterSetId,
	SSHORT& characterLengthNull, SSHORT& characterLength,
	SSHORT& fieldPrecisionNull, SSHORT& fieldPrecision,
	SSHORT& collationIdNull, SSHORT& collationId,
	SSHORT& segmentLengthNull, SSHORT& segmentLength)
{
	// Initialize all nullable fields.
	fieldSubTypeNull = fieldScaleNull = characterSetIdNull = characterLengthNull =
		fieldPrecisionNull = collationIdNull = segmentLengthNull = TRUE;

	if (type->dtype == dtype_blob)
	{
		fieldSubTypeNull = FALSE;
		fieldSubType = type->subType;

		fieldScaleNull = FALSE;
		fieldScale = 0;

		if (type->subType == isc_blob_text)
		{
			characterSetIdNull = FALSE;
			characterSetId = type->charSetId.value;

			collationIdNull = FALSE;
			collationId = type->collationId;
		}

		if (type->segLength != 0)
		{
			segmentLengthNull = FALSE;
			segmentLength = type->segLength;
		}
	}
	else if (type->dtype <= dtype_any_text)
	{
		fieldSubTypeNull = FALSE;
		fieldSubType = type->subType;

		fieldScaleNull = FALSE;
		fieldScale = 0;

		if (type->charLength != 0)
		{
			characterLengthNull = FALSE;
			characterLength = type->charLength;
		}

		characterSetIdNull = FALSE;
		characterSetId = type->charSetId.value;

		collationIdNull = FALSE;
		collationId = type->collationId;
	}
	else
	{
		fieldScaleNull = FALSE;
		fieldScale = type->scale;

		if (DTYPE_IS_EXACT(type->dtype))
		{
			fieldPrecisionNull = FALSE;
			fieldPrecision = type->precision;

			fieldSubTypeNull = FALSE;
			fieldSubType = type->subType;
		}

		if (DTYPE_IS_DECFLOAT(type->dtype))
		{
			fieldPrecisionNull = FALSE;
			fieldPrecision = type->precision;
		}
	}

	if (type->dtype == dtype_varying)
	{
		fb_assert(type->length <= MAX_SSHORT);
		fieldLength = (SSHORT) (type->length - sizeof(USHORT));
	}
	else
		fieldLength = type->length;

	fieldType = blr_dtypes[type->dtype];
}


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


// Delete a security class.
bool DdlNode::deleteSecurityClass(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& secClass)
{
	AutoCacheRequest request(tdbb, drq_e_class, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		SC IN RDB$SECURITY_CLASSES
		WITH SC.RDB$SECURITY_CLASS EQ secClass.c_str()
	{
		found = true;
		ERASE SC;
	}
	END_FOR

	return found;
}

void DdlNode::storePrivileges(thread_db* tdbb, jrd_tra* transaction,
							  const MetaName& name, int type,
							  const char* privileges)
{
	Attachment* const attachment = transaction->tra_attachment;
	const MetaString& ownerName = attachment->getEffectiveUserName();

	AutoCacheRequest request(tdbb, drq_s_usr_prvs, DYN_REQUESTS);

	for (const char* p = privileges; *p; ++p)
	{
		STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			X IN RDB$USER_PRIVILEGES
		{
			strcpy(X.RDB$RELATION_NAME, name.c_str());
			strcpy(X.RDB$USER, ownerName.c_str());
			X.RDB$USER_TYPE = obj_user;
			X.RDB$OBJECT_TYPE = type;
			X.RDB$PRIVILEGE[0] = *p;
			X.RDB$PRIVILEGE[1] = 0;
			X.RDB$GRANT_OPTION = WITH_GRANT_OPTION;
		}
		END_STORE
	}
}

void DdlNode::executeDdlTrigger(thread_db* tdbb, jrd_tra* transaction, DdlTriggerWhen when,
	int action, const MetaName& objectName, const MetaName& oldNewObjectName, const string& sqlText)
{
	Attachment* const attachment = transaction->tra_attachment;

 	// do nothing if user doesn't want database triggers
	if (attachment->att_flags & ATT_no_db_triggers)
		return;

	fb_assert(action > 0);	// first element is NULL
	DdlTriggerContext context;
	context.eventType = DDL_TRIGGER_ACTION_NAMES[action][0];
	context.objectType = DDL_TRIGGER_ACTION_NAMES[action][1];
	context.objectName = objectName;
	context.sqlText = sqlText;

	if (oldNewObjectName.hasData())
	{
		context.oldObjectName = when == DTW_BEFORE ? objectName : oldNewObjectName;
		context.newObjectName = when == DTW_BEFORE ? oldNewObjectName : objectName;
	}

	Stack<DdlTriggerContext*>::AutoPushPop autoContext(attachment->ddlTriggersContext, &context);
	AutoSavePoint savePoint(tdbb, transaction);

	EXE_execute_ddl_triggers(tdbb, transaction, when == DTW_BEFORE, action);

	savePoint.release();	// everything is ok
}

void DdlNode::executeDdlTrigger(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, DdlTriggerWhen when, int action, const MetaName& objectName,
	const MetaName& oldNewObjectName)
{
	executeDdlTrigger(tdbb, transaction, when, action, objectName, oldNewObjectName,
		*dsqlScratch->getStatement()->getSqlText());
}

void DdlNode::storeGlobalField(thread_db* tdbb, jrd_tra* transaction, MetaName& name,
	const TypeClause* field, const string& computedSource, const BlrDebugWriter::BlrData& computedValue)
{
	Attachment* const attachment = transaction->tra_attachment;
	const MetaString& ownerName = attachment->getEffectiveUserName();

	const ValueListNode* elements = field->ranges;
	const USHORT dims = elements ? elements->items.getCount() / 2 : 0;

	if (dims > MAX_ARRAY_DIMENSIONS)
	{
		status_exception::raise(
			Arg::Gds(isc_sqlerr) << Arg::Num(-604) <<
			Arg::Gds(isc_dsql_max_arr_dim_exceeded));
	}

	if (name.isEmpty())
		DYN_UTIL_generate_field_name(tdbb, name);

	AutoCacheRequest requestHandle(tdbb, drq_s_fld_src, DYN_REQUESTS);

	STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		FLD IN RDB$FIELDS
	{
		FLD.RDB$SYSTEM_FLAG = 0;
		strcpy(FLD.RDB$FIELD_NAME, name.c_str());

		FLD.RDB$OWNER_NAME.NULL = FALSE;
		strcpy(FLD.RDB$OWNER_NAME, ownerName.c_str());

		FLD.RDB$COMPUTED_SOURCE.NULL = TRUE;
		FLD.RDB$COMPUTED_BLR.NULL = TRUE;
		FLD.RDB$DIMENSIONS.NULL = TRUE;

		updateRdbFields(field,
			FLD.RDB$FIELD_TYPE,
			FLD.RDB$FIELD_LENGTH,
			FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
			FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
			FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
			FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
			FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
			FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
			FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);

		if (dims != 0)
		{
			FLD.RDB$DIMENSIONS.NULL = FALSE;
			FLD.RDB$DIMENSIONS = dims;
		}

		if (computedSource.hasData())
		{
			FLD.RDB$COMPUTED_SOURCE.NULL = FALSE;
			attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$COMPUTED_SOURCE,
				computedSource);
		}

		if (computedValue.hasData())
		{
			FLD.RDB$COMPUTED_BLR.NULL = FALSE;
			attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR,
				computedValue);
		}
	}
	END_STORE

	if (elements)	// Is the type an array?
	{
		requestHandle.reset(tdbb, drq_s_fld_dym, DYN_REQUESTS);

		SSHORT position = 0;
		const NestConst<ValueExprNode>* ptr = elements->items.begin();
		for (const NestConst<ValueExprNode>* const end = elements->items.end();
			 ptr != end;
			 ++ptr, ++position)
		{
			const ValueExprNode* element = *ptr++;
			const SLONG lrange = nodeAs<LiteralNode>(element)->getSlong();
			element = *ptr;
			const SLONG hrange = nodeAs<LiteralNode>(element)->getSlong();

			if (lrange >= hrange)
			{
				status_exception::raise(
					Arg::Gds(isc_sqlerr) << Arg::Num(-604) <<
					Arg::Gds(isc_dsql_arr_range_error));
			}

			STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
				DIM IN RDB$FIELD_DIMENSIONS
			{
				strcpy(DIM.RDB$FIELD_NAME, name.c_str());
				DIM.RDB$DIMENSION = position;
				DIM.RDB$UPPER_BOUND = hrange;
				DIM.RDB$LOWER_BOUND = lrange;
			}
			END_STORE
		}
	}

	storePrivileges(tdbb, transaction, name, obj_field, USAGE_PRIVILEGES);
}


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


ParameterClause::ParameterClause(MemoryPool& pool, dsql_fld* field, const MetaName& aCollate,
			ValueSourceClause* aDefaultClause, ValueExprNode* aParameterExpr)
	: name(pool, field ? field->fld_name : ""),
	  type(field),
	  defaultClause(aDefaultClause),
	  parameterExpr(aParameterExpr)
{
	type->collate = aCollate;
}

string ParameterClause::internalPrint(NodePrinter& printer) const
{
	NODE_PRINT(printer, name);
	NODE_PRINT(printer, type);
	NODE_PRINT(printer, defaultClause);
	NODE_PRINT(printer, parameterExpr);
	NODE_PRINT(printer, udfMechanism);

	return "ParameterClause";
}


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


string AlterCharSetNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, charSet);
	NODE_PRINT(printer, defaultCollation);

	return "AlterCharSetNode";
}

void AlterCharSetNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_charset(tdbb, charSet, SCL_alter);
}

void AlterCharSetNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	METD_drop_charset(transaction, charSet);
	MET_dsql_cache_release(tdbb, SYM_intlsym_charset, charSet);

	bool charSetFound = false;
	bool collationFound = false;

	AutoCacheRequest requestHandle1(tdbb, drq_m_charset, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle1 TRANSACTION_HANDLE transaction)
		CS IN RDB$CHARACTER_SETS
		WITH CS.RDB$CHARACTER_SET_NAME EQ charSet.c_str()
	{
		charSetFound = true;

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_ALTER_CHARACTER_SET, charSet, NULL);

		AutoCacheRequest requestHandle2(tdbb, drq_l_collation, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
			COLL IN RDB$COLLATIONS
			WITH COLL.RDB$CHARACTER_SET_ID EQ CS.RDB$CHARACTER_SET_ID AND
				COLL.RDB$COLLATION_NAME EQ defaultCollation.c_str()
		{
			collationFound = true;
		}
		END_FOR

		if (collationFound)
		{
			MODIFY CS
				CS.RDB$DEFAULT_COLLATE_NAME.NULL = FALSE;
				strcpy(CS.RDB$DEFAULT_COLLATE_NAME, defaultCollation.c_str());
			END_MODIFY
		}
	}
	END_FOR

	if (!charSetFound)
		status_exception::raise(Arg::Gds(isc_charset_not_found) << Arg::Str(charSet));

	if (!collationFound)
	{
		status_exception::raise(
			Arg::Gds(isc_collation_not_found) << Arg::Str(defaultCollation) << Arg::Str(charSet));
	}

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
		DDL_TRIGGER_ALTER_CHARACTER_SET, charSet, NULL);
}


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


void AlterEDSPoolSetNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	if (!tdbb->getAttachment()->locksmith(tdbb, MODIFY_EXT_CONN_POOL))
		status_exception::raise(Arg::Gds(isc_miss_prvlg) << "MODIFY_EXT_CONN_POOL");
}

string AlterEDSPoolSetNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, m_param);
	NODE_PRINT(printer, m_value);

	return "AlterEDSPoolSetNode";
}

void AlterEDSPoolSetNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* /*transaction*/)
{
	EDS::ConnectionsPool* pool = EDS::Manager::getConnPool(true);
	switch (m_param)
	{
	case POOL_SIZE:
		pool->setMaxCount(m_value);
		break;

	case POOL_LIFETIME:
		pool->setLifeTime(m_value);
		break;

	default:
		status_exception::raise(
			Arg::Gds(isc_random) << Arg::Str("Unknown param for ALTER EXTERNAL CONNECTIONS POOL statement"));
	}
}


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


void AlterEDSPoolClearNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	if (!tdbb->getAttachment()->locksmith(tdbb, MODIFY_EXT_CONN_POOL))
		status_exception::raise(Arg::Gds(isc_miss_prvlg) << "MODIFY_EXT_CONN_POOL");
}

string AlterEDSPoolClearNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, m_param);
	NODE_PRINT(printer, m_value);

	return "AlterEDSPoolClearNode";
}

void AlterEDSPoolClearNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* /*transaction*/)
{
	EDS::ConnectionsPool* pool = EDS::Manager::getConnPool(false);
	if (!pool)
		return;

	switch (m_param)
	{
	case POOL_ALL:
	{
		pool->clearIdle(tdbb, true);
		break;
	}

	case POOL_OLDEST:
	{
		pool->clearIdle(tdbb, false);
		break;
	}

	case POOL_DB:
		//break;

	default:
		status_exception::raise(
			Arg::Gds(isc_random) << Arg::Str("Unknown param for ALTER EXTERNAL CONNECTIONS POOL statement"));
	}
}


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


string CommentOnNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, objType);
	NODE_PRINT(printer, objName);
	NODE_PRINT(printer, subName);
	NODE_PRINT(printer, text);
	NODE_PRINT(printer, str);

	return "CommentOnNode";
}

void CommentOnNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;

	Arg::StatusVector status;
	string objNameStr = objName.toString();

	if (objType == obj_parameter)
	{
		fb_assert(subName.hasData());

		AutoRequest requestHandle;

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			FUN IN RDB$FUNCTIONS CROSS
			ARG IN RDB$FUNCTION_ARGUMENTS
			WITH FUN.RDB$FUNCTION_NAME EQ objName.identifier.c_str() AND
				 FUN.RDB$PACKAGE_NAME EQUIV NULLIF(objName.package.c_str(), '') AND
				 ARG.RDB$FUNCTION_NAME EQ FUN.RDB$FUNCTION_NAME AND
				 ARG.RDB$PACKAGE_NAME EQUIV FUN.RDB$PACKAGE_NAME AND
				 ARG.RDB$ARGUMENT_NAME EQ subName.c_str()
		{
			objType = obj_udf;
		}
		END_FOR

		requestHandle.reset();

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			PRC IN RDB$PROCEDURES CROSS
			PRM IN RDB$PROCEDURE_PARAMETERS
			WITH PRC.RDB$PROCEDURE_NAME EQ objName.identifier.c_str() AND
				 PRC.RDB$PACKAGE_NAME EQUIV NULLIF(objName.package.c_str(), '') AND
				 PRM.RDB$PROCEDURE_NAME EQ PRC.RDB$PROCEDURE_NAME AND
				 PRM.RDB$PACKAGE_NAME EQUIV PRC.RDB$PACKAGE_NAME AND
				 PRM.RDB$PARAMETER_NAME EQ subName.c_str()
		{
			if (objType == obj_parameter)
				objType = obj_procedure;
			else
			{
				status_exception::raise(Arg::Gds(isc_dyn_routine_param_ambiguous) <<
					Arg::Str(subName) << Arg::Str(objNameStr));
			}
		}
		END_FOR

		if (objType == obj_parameter)
		{
			status_exception::raise(Arg::Gds(isc_dyn_routine_param_not_found) <<
				Arg::Str(subName) << Arg::Str(objNameStr));
		}
	}

	dsc dscName;
	MetaName relationName;

	switch (objType)
	{
		case obj_schema:
		case obj_database:
			SCL_check_database(tdbb, SCL_alter);
			break;

		case obj_field:
			SCL_check_domain(tdbb, objName.identifier, SCL_alter);
			break;

		case obj_relation:
			dscName.makeText(objNameStr.length(), CS_METADATA, (UCHAR*) objName.identifier.c_str());
			SCL_check_relation(tdbb, &dscName, SCL_alter);
			break;

		case obj_view:
			dscName.makeText(objNameStr.length(), CS_METADATA, (UCHAR*) objName.identifier.c_str());
			SCL_check_view(tdbb, &dscName, SCL_alter);
			break;

		case obj_procedure:
			dscName.makeText(objNameStr.length(), CS_METADATA, (UCHAR*) objName.identifier.c_str());
			SCL_check_procedure(tdbb, &dscName, SCL_alter);
			break;

		case obj_trigger:
			relationName = getTriggerRelationName(tdbb, transaction, objName.identifier);
			if (relationName.isEmpty())
				SCL_check_database(tdbb, SCL_alter);
			else
			{
				dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());
				SCL_check_relation(tdbb, &dscName, SCL_alter);
			}
			break;

		case obj_udf:
			dscName.makeText(objName.identifier.length(), CS_METADATA, (UCHAR*) objName.identifier.c_str());
			SCL_check_function(tdbb, &dscName, SCL_alter);
			break;

		case obj_blob_filter:
			SCL_check_filter(tdbb, objName.identifier, SCL_alter);
			break;

		case obj_exception:
			SCL_check_exception(tdbb, objName.identifier, SCL_alter);
			break;

		case obj_generator:
			SCL_check_generator(tdbb, objName.identifier, SCL_alter);
			break;

		case obj_index:
			bool systemIndex;
			relationName = getIndexRelationName(tdbb, transaction, objName.identifier, systemIndex);
			dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());
			SCL_check_relation(tdbb, &dscName, SCL_alter, systemIndex);
			break;

		case obj_sql_role:
			SCL_check_role(tdbb, objName.identifier, SCL_alter);
			break;

		case obj_charset:
			SCL_check_charset(tdbb, objName.identifier, SCL_alter);
			break;

		case obj_collation:
			SCL_check_collation(tdbb, objName.identifier, SCL_alter);
			break;

		case obj_package_header:
			dscName.makeText(objName.identifier.length(), CS_METADATA, (UCHAR*) objName.identifier.c_str());
			SCL_check_package(tdbb, &dscName, SCL_alter);
			break;

		default:
			fb_assert(false);
	}
}

// select rdb$relation_name from rdb$relation_fields where rdb$field_name = 'RDB$DESCRIPTION';
// gives the list of objects that accept descriptions. At FB2 time, the only
// subobjects with descriptions are relation's fields and procedure's parameters.
// In FB3 we added function's arguments.
void CommentOnNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;

	const char* tableClause = NULL;
	const char* columnClause = NULL;
	const char* subColumnClause = NULL;
	const char* addWhereClause = NULL;
	Arg::StatusVector status;
	string objNameStr = objName.toString();

	fb_assert(objType != obj_parameter);

	switch (objType)
	{
		case obj_database:
			tableClause = "rdb$database";
			break;

		case obj_field:
			tableClause = "rdb$fields";
			columnClause = "rdb$field_name";
			status << Arg::Gds(isc_dyn_domain_not_found);
			break;

		case obj_relation:
			if (subName.hasData())
			{
				tableClause = "rdb$relation_fields";
				subColumnClause = "rdb$field_name";
				status << Arg::Gds(isc_dyn_column_does_not_exist) <<
					Arg::Str(subName) << Arg::Str(objNameStr);
			}
			else
			{
				tableClause = "rdb$relations";
				addWhereClause = "rdb$view_blr is null";
				status << Arg::Gds(isc_dyn_table_not_found) << Arg::Str(objNameStr);
			}
			columnClause = "rdb$relation_name";
			break;

		case obj_view:
			tableClause = "rdb$relations";
			columnClause = "rdb$relation_name";
			status << Arg::Gds(isc_dyn_view_not_found) << Arg::Str(objNameStr);
			addWhereClause = "rdb$view_blr is not null";
			break;

		case obj_procedure:
			if (subName.hasData())
			{
				tableClause = "rdb$procedure_parameters";
				subColumnClause = "rdb$parameter_name";
				status << Arg::Gds(isc_dyn_proc_param_not_found) <<
					Arg::Str(subName) << Arg::Str(objNameStr);
			}
			else
			{
				tableClause = "rdb$procedures";
				status << Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(objNameStr);
			}

			columnClause = "rdb$procedure_name";
			break;

		case obj_trigger:
			tableClause = "rdb$triggers";
			columnClause = "rdb$trigger_name";
			status << Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(objNameStr);
			break;

		case obj_udf:
			if (subName.hasData())
			{
				tableClause = "rdb$function_arguments";
				subColumnClause = "rdb$argument_name";
				status << Arg::Gds(isc_dyn_func_param_not_found) <<
					Arg::Str(subName) << Arg::Str(objNameStr);
			}
			else
			{
				tableClause = "rdb$functions";
				status << Arg::Gds(isc_dyn_func_not_found) << Arg::Str(objNameStr);
			}

			columnClause = "rdb$function_name";
			break;

		case obj_blob_filter:
			tableClause = "rdb$filters";
			columnClause = "rdb$function_name";
			status << Arg::Gds(isc_dyn_filter_not_found) << Arg::Str(objNameStr);
			break;

		case obj_exception:
			tableClause = "rdb$exceptions";
			columnClause = "rdb$exception_name";
			status << Arg::Gds(isc_dyn_exception_not_found) << Arg::Str(objNameStr);
			break;

		case obj_generator:
			tableClause = "rdb$generators";
			columnClause = "rdb$generator_name";
			status << Arg::Gds(isc_dyn_gen_not_found) << Arg::Str(objNameStr);
			break;

		case obj_index:
			tableClause = "rdb$indices";
			columnClause = "rdb$index_name";
			status << Arg::Gds(isc_dyn_index_not_found) << Arg::Str(objNameStr);
			break;

		case obj_sql_role:
			tableClause = "rdb$roles";
			columnClause = "rdb$role_name";
			status << Arg::Gds(isc_dyn_role_not_found) << Arg::Str(objNameStr);
			break;

		case obj_charset:
			tableClause = "rdb$character_sets";
			columnClause = "rdb$character_set_name";
			status << Arg::Gds(isc_dyn_charset_not_found) << Arg::Str(objNameStr);
			break;

		case obj_collation:
			tableClause = "rdb$collations";
			columnClause = "rdb$collation_name";
			status << Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(objNameStr);
			break;

		case obj_package_header:
			tableClause = "rdb$packages";
			columnClause = "rdb$package_name";
			status << Arg::Gds(isc_dyn_package_not_found) << Arg::Str(objNameStr);
			break;

		case obj_schema:
			tableClause = "rdb$schemas";
			columnClause = "rdb$schema_name";
			status << Arg::Gds(isc_dyn_schema_not_found) << Arg::Str(objNameStr);
			break;

		default:
			fb_assert(false);
			return;
	}

	Nullable<string> description;
	if (text.hasData())
		description = text;

	PreparedStatement::Builder sql;
	sql << "update" << tableClause << "set rdb$description =" << description << "where 1 = 1";

	if (columnClause)
	{
		sql << "and" << columnClause << "=" << objName.identifier;

		if (subColumnClause)
			sql << "and" << subColumnClause << "=" << subName;
	}

	if (objType == obj_procedure || objType == obj_udf)
		sql << "and rdb$package_name is not distinct from nullif(" << objName.package << ", '')";

	if (addWhereClause)
		sql << "and" << addWhereClause;

	AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql));

	if (ps->executeUpdate(tdbb, transaction) == 0)
		status_exception::raise(status);
}


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


string CreateAlterFunctionNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, create);
	NODE_PRINT(printer, alter);
	NODE_PRINT(printer, external);
	NODE_PRINT(printer, deterministic);
	NODE_PRINT(printer, parameters);
	NODE_PRINT(printer, returnType);
	NODE_PRINT(printer, localDeclList);
	NODE_PRINT(printer, source);
	NODE_PRINT(printer, body);
	NODE_PRINT(printer, compiled);
	NODE_PRINT(printer, invalid);
	NODE_PRINT(printer, package);
	NODE_PRINT(printer, packageOwner);
	NODE_PRINT(printer, privateScope);
	NODE_PRINT(printer, udfReturnPos);

	return "CreateAlterFunctionNode";
}

DdlNode* CreateAlterFunctionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION);

	// check for duplicated parameters and declaration names

	StrArray names;

	for (FB_SIZE_T i = 0; i < parameters.getCount(); ++i)
	{
		const ParameterClause* const parameter = parameters[i];

		if (names.exist(parameter->name.c_str()))
		{
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
				Arg::Gds(isc_dsql_duplicate_spec) << Arg::Str(parameter->name));
		}

		if (parameter->name.hasData())	// legacy UDFs has unnamed parameters
			names.add(parameter->name.c_str());
	}

	PASS1_check_unique_fields_names(names, localDeclList);

	source.ltrim("\n\r\t ");

	bool hasDefaultParams = false;

	// compile default expressions
	for (unsigned i = 0; i < parameters.getCount(); ++i)
	{
		ParameterClause* parameter = parameters[i];

		if (parameter->defaultClause)
		{
			hasDefaultParams = true;
			parameter->defaultClause->value = doDsqlPass(dsqlScratch, parameter->defaultClause->value);
		}
		else if (hasDefaultParams)
		{
			// parameter without default value after parameters with default
			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
					  Arg::Gds(isc_bad_default_value) <<
					  Arg::Gds(isc_invalid_clause) << Arg::Str("defaults must be last"));
		}
	}

	for (unsigned i = 0; i < parameters.getCount(); ++i)
		parameters[i]->type->resolve(dsqlScratch);

	if (returnType && returnType->type)
		returnType->type->resolve(dsqlScratch);

	// check SQL SECURITY is not set if function declared in package
	if (package.hasData() && ssDefiner.specified)
	{
		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
				  Arg::Gds(isc_invalid_clause) << Arg::Str("SQL SECURITY for functions is prohibit in packages"));
	}

	return DdlNode::dsqlPass(dsqlScratch);
}

void CreateAlterFunctionNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	if (alter)
	{
		if (SCL_check_function(tdbb, &dscName, SCL_alter) || !create)
			return;
	}

	SCL_check_create_access(tdbb, SCL_object_function);
}

void CreateAlterFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	fb_assert(create || alter);

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);
	bool altered = false;

	// first pass
	if (alter)
	{
		if (executeAlter(tdbb, dsqlScratch, transaction, false, true))
		{
			altered = true;
		}
		else
		{
			if (create)	// create or alter
				executeCreate(tdbb, dsqlScratch, transaction);
			else
				status_exception::raise(Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name));
		}
	}
	else
		executeCreate(tdbb, dsqlScratch, transaction);

	compile(tdbb, dsqlScratch);

	executeAlter(tdbb, dsqlScratch, transaction, true, false);	// second pass

	if (package.isEmpty())
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
			(altered ? DDL_TRIGGER_ALTER_FUNCTION : DDL_TRIGGER_CREATE_FUNCTION), name, NULL);
	}

	savePoint.release();	// everything is ok

	if (alter)
	{
		// Update DSQL cache
		METD_drop_function(transaction, QualifiedName(name, package));
		MET_dsql_cache_release(tdbb, SYM_udf, name, package);
	}
}

void CreateAlterFunctionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->getAttachment();
	const MetaString& ownerName = attachment->getEffectiveUserName();

	if (package.isEmpty())
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_CREATE_FUNCTION, name, NULL);

		DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_udf);
	}

	AutoCacheRequest requestHandle(tdbb, drq_s_funcs2, DYN_REQUESTS);

	int faults = 0;

	while (true)
	{
		try
		{
			SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_fun_id, "RDB$FUNCTIONS");
			id %= (MAX_SSHORT + 1);

			if (!id)
				continue;

			STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
				FUN IN RDB$FUNCTIONS
			{
				FUN.RDB$FUNCTION_ID = id;
				FUN.RDB$SYSTEM_FLAG = 0;
				strcpy(FUN.RDB$FUNCTION_NAME, name.c_str());

				if (package.hasData())
				{
					FUN.RDB$PACKAGE_NAME.NULL = FALSE;
					strcpy(FUN.RDB$PACKAGE_NAME, package.c_str());

					FUN.RDB$PRIVATE_FLAG.NULL = FALSE;
					FUN.RDB$PRIVATE_FLAG = privateScope;

					FUN.RDB$OWNER_NAME.NULL = FALSE;
					strcpy(FUN.RDB$OWNER_NAME, packageOwner.c_str());
				}
				else
				{
					FUN.RDB$PACKAGE_NAME.NULL = TRUE;
					FUN.RDB$PRIVATE_FLAG.NULL = TRUE;

					FUN.RDB$OWNER_NAME.NULL = FALSE;
					strcpy(FUN.RDB$OWNER_NAME, ownerName.c_str());
				}

				FUN.RDB$LEGACY_FLAG.NULL = FALSE;
				FUN.RDB$LEGACY_FLAG = isUdf() ? TRUE : FALSE;

				FUN.RDB$RETURN_ARGUMENT.NULL = FALSE;
				FUN.RDB$RETURN_ARGUMENT = 0;
			}
			END_STORE

			break;
		}
		catch (const status_exception& ex)
		{
			if (ex.value()[1] != isc_unique_key_violation)
				throw;

			if (++faults > MAX_SSHORT)
				throw;

			fb_utils::init_status(tdbb->tdbb_status_vector);
		}
	}

	if (package.isEmpty())
		storePrivileges(tdbb, transaction, name, obj_udf, EXEC_PRIVILEGES);

	executeAlter(tdbb, dsqlScratch, transaction, false, false);
}

bool CreateAlterFunctionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, bool secondPass, bool runTriggers)
{
	Attachment* const attachment = transaction->getAttachment();

	bool modified = false;
	unsigned returnPos = 0;

	AutoCacheRequest requestHandle(tdbb, drq_m_funcs2, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		FUN IN RDB$FUNCTIONS
		WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND
			 FUN.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
	{
		if (FUN.RDB$SYSTEM_FLAG)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_cannot_mod_sysfunc) << MetaName(FUN.RDB$FUNCTION_NAME));
		}

		if (!secondPass && runTriggers && package.isEmpty())
		{
			executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
				DDL_TRIGGER_ALTER_FUNCTION, name, NULL);
		}

		MODIFY FUN
			if (secondPass)
			{
				FUN.RDB$FUNCTION_BLR.NULL = TRUE;
				FUN.RDB$DEBUG_INFO.NULL = TRUE;
			}
			else
			{
				FUN.RDB$ENGINE_NAME.NULL = TRUE;
				FUN.RDB$MODULE_NAME.NULL = TRUE;
				FUN.RDB$ENTRYPOINT.NULL = TRUE;
				FUN.RDB$VALID_BLR.NULL = TRUE;

				FUN.RDB$FUNCTION_SOURCE.NULL = !(source.hasData() && (external || package.isEmpty()));
				if (!FUN.RDB$FUNCTION_SOURCE.NULL)
					attachment->storeMetaDataBlob(tdbb, transaction, &FUN.RDB$FUNCTION_SOURCE, source);

				FUN.RDB$DETERMINISTIC_FLAG.NULL = FALSE;
				FUN.RDB$DETERMINISTIC_FLAG = deterministic ? TRUE : FALSE;
				FUN.RDB$RETURN_ARGUMENT = 0;

				if (ssDefiner.specified)
				{
					FUN.RDB$SQL_SECURITY.NULL = FALSE;
					FUN.RDB$SQL_SECURITY = ssDefiner.value ? FB_TRUE : FB_FALSE;
				}
				else
					FUN.RDB$SQL_SECURITY.NULL = TRUE;

				FUN.RDB$LEGACY_FLAG.NULL = FALSE;
				FUN.RDB$LEGACY_FLAG = isUdf() ? TRUE : FALSE;

				if (isUdf())
				{
					dsql_fld* field = returnType ? returnType->type : NULL;

					if (field)
					{
						// CVC: This is case of "returns <type> [by value|reference]".
						// Some data types can not be returned as value.

						if (returnType->udfMechanism.value == FUN_value &&
							(field->dtype == dtype_text || field->dtype == dtype_varying ||
							 field->dtype == dtype_cstring || field->dtype == dtype_blob ||
							 field->dtype == dtype_timestamp))
						{
							// Return mode by value not allowed for this data type.
							status_exception::raise(
								Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
								Arg::Gds(isc_dsql_command_err) <<
								Arg::Gds(isc_return_mode_err));
						}

						// For functions returning a blob, coerce return argument position to
						// be the last parameter.

						if (field->dtype == dtype_blob)
						{
							returnPos = unsigned(parameters.getCount()) + 1;
							if (returnPos > MAX_UDF_ARGUMENTS)
							{
								// External functions can not have more than 10 parameters
								// Or 9 if the function returns a BLOB.
								status_exception::raise(
									Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
									Arg::Gds(isc_dsql_command_err) <<
									Arg::Gds(isc_extern_func_err));
							}

							FUN.RDB$RETURN_ARGUMENT = (SSHORT) returnPos;
						}
						else
							FUN.RDB$RETURN_ARGUMENT = 0;
					}
					else	// CVC: This is case of "returns parameter <N>"
					{
						// Function modifies an argument whose value is the function return value.

						if (udfReturnPos < 1 || ULONG(udfReturnPos) > parameters.getCount())
						{
							// CVC: We should devise new msg "position should be between 1 and #params";
							// here it is: dsql_udf_return_pos_err

							// External functions can not have more than 10 parameters
							// Not strictly correct -- return position error
							status_exception::raise(
								Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
								Arg::Gds(isc_dsql_command_err) <<	// gds__extern_func_err
								Arg::Gds(isc_dsql_udf_return_pos_err) << Arg::Num(parameters.getCount()));
						}

						// We'll verify that SCALAR_ARRAY can't be used as a return type.
						// The support for SCALAR_ARRAY is only for input parameters.

						if (parameters[udfReturnPos - 1]->udfMechanism.specified &&
							parameters[udfReturnPos - 1]->udfMechanism.value == FUN_scalar_array)
						{
							status_exception::raise(
								Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
								Arg::Gds(isc_dsql_command_err) <<
								Arg::Gds(isc_random) << "BY SCALAR_ARRAY can't be used as a return parameter");
						}

						FUN.RDB$RETURN_ARGUMENT = (SSHORT) udfReturnPos;
					}
				}
			}

			if (external)
			{
				if (!secondPass)
				{
					FUN.RDB$ENGINE_NAME.NULL = (SSHORT) external->engine.isEmpty();
					external->engine.copyTo(FUN.RDB$ENGINE_NAME, sizeof(FUN.RDB$ENGINE_NAME));

					if (external->udfModule.length() >= sizeof(FUN.RDB$MODULE_NAME))
						status_exception::raise(Arg::Gds(isc_dyn_name_longer));

					FUN.RDB$MODULE_NAME.NULL = (SSHORT) external->udfModule.isEmpty();
					external->udfModule.copyTo(FUN.RDB$MODULE_NAME, sizeof(FUN.RDB$MODULE_NAME));

					if (external->name.length() >= sizeof(FUN.RDB$ENTRYPOINT))
						status_exception::raise(Arg::Gds(isc_dyn_name_longer));

					FUN.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
					external->name.copyTo(FUN.RDB$ENTRYPOINT, sizeof(FUN.RDB$ENTRYPOINT));
				}
			}
			else if (body)
			{
				if (secondPass)
				{
					FUN.RDB$VALID_BLR.NULL = FALSE;
					FUN.RDB$VALID_BLR = TRUE;

					FUN.RDB$FUNCTION_BLR.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$FUNCTION_BLR,
						dsqlScratch->getBlrData());

					FUN.RDB$DEBUG_INFO.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction, &FUN.RDB$DEBUG_INFO,
						dsqlScratch->getDebugData());
				}
			}

			if (package.hasData())
			{
				FUN.RDB$PRIVATE_FLAG.NULL = FALSE;
				FUN.RDB$PRIVATE_FLAG = privateScope;
			}
			else
				FUN.RDB$PRIVATE_FLAG.NULL = TRUE;

			modified = true;
		END_MODIFY
	}
	END_FOR

	if (!secondPass && modified)
	{
		// Get all comments and defaults from the old parameter list.
		CollectedParameterMap collectedParameters;
		collectParameters(tdbb, transaction, collectedParameters);

		// delete all old arguments
		DropFunctionNode::dropArguments(tdbb, transaction, name, package);

		// and insert the new ones

		if (returnType && returnType->type)
			storeArgument(tdbb, dsqlScratch, transaction, returnPos, true, returnType, NULL);

		for (FB_SIZE_T i = 0; i < parameters.getCount(); ++i)
		{
			ParameterClause* parameter = parameters[i];
			CollectedParameter collectedParameter;

			// Find the original parameter to recreate in the new parameter.
			collectedParameters.get(parameter->name, collectedParameter);

			storeArgument(tdbb, dsqlScratch, transaction, i + 1, false,
				parameter, &collectedParameter);
		}
	}

	return modified;
}

void CreateAlterFunctionNode::storeArgument(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, unsigned pos, bool returnArg, ParameterClause* parameter,
	const CollectedParameter* collectedParameter)
{
	Attachment* const attachment = transaction->getAttachment();
	TypeClause* type = parameter->type;

	AutoCacheRequest requestHandle(tdbb, drq_s_func_args2, DYN_REQUESTS);

	STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		ARG IN RDB$FUNCTION_ARGUMENTS
	{
		ARG.RDB$FUNCTION_NAME.NULL = FALSE;
		strcpy(ARG.RDB$FUNCTION_NAME, name.c_str());

		ARG.RDB$ARGUMENT_NAME.NULL = (SSHORT) parameter->name.isEmpty();
		strcpy(ARG.RDB$ARGUMENT_NAME, parameter->name.c_str());

		ARG.RDB$PACKAGE_NAME.NULL = (SSHORT) package.isEmpty();
		strcpy(ARG.RDB$PACKAGE_NAME, package.c_str());

		ARG.RDB$SYSTEM_FLAG = 0;
		ARG.RDB$SYSTEM_FLAG.NULL = FALSE;

		ARG.RDB$ARGUMENT_POSITION.NULL = FALSE;
		ARG.RDB$ARGUMENT_POSITION = pos;

		ARG.RDB$NULL_FLAG.NULL = TRUE;
		ARG.RDB$RELATION_NAME.NULL = TRUE;
		ARG.RDB$FIELD_NAME.NULL = TRUE;
		ARG.RDB$FIELD_SOURCE.NULL = TRUE;
		ARG.RDB$DEFAULT_VALUE.NULL = TRUE;
		ARG.RDB$DEFAULT_SOURCE.NULL = TRUE;

		ARG.RDB$MECHANISM.NULL = TRUE;
		ARG.RDB$FIELD_TYPE.NULL = TRUE;
		ARG.RDB$FIELD_LENGTH.NULL = TRUE;
		ARG.RDB$FIELD_SUB_TYPE.NULL = TRUE;
		ARG.RDB$FIELD_PRECISION.NULL = TRUE;
		ARG.RDB$FIELD_SCALE.NULL = TRUE;
		ARG.RDB$CHARACTER_SET_ID.NULL = TRUE;
		ARG.RDB$CHARACTER_LENGTH.NULL = TRUE;
		ARG.RDB$COLLATION_ID.NULL = TRUE;
		ARG.RDB$ARGUMENT_MECHANISM.NULL = TRUE;

		if (!isUdf())
		{
			ARG.RDB$ARGUMENT_MECHANISM.NULL = FALSE;
			ARG.RDB$ARGUMENT_MECHANISM = (USHORT) (type->fullDomain || type->typeOfName.isEmpty() ?
				prm_mech_normal : prm_mech_type_of);
		}

		if (type->notNull)
		{
			ARG.RDB$NULL_FLAG.NULL = FALSE;
			ARG.RDB$NULL_FLAG = TRUE;
		}

		if (type->typeOfTable.isEmpty())
		{
			if (type->typeOfName.hasData())
			{
				ARG.RDB$FIELD_SOURCE.NULL = FALSE;
				strcpy(ARG.RDB$FIELD_SOURCE, type->typeOfName.c_str());
			}
			else
			{
				if (isUdf())
				{
					SSHORT segmentLength, segmentLengthNull;

					ARG.RDB$FIELD_TYPE.NULL = FALSE;
					ARG.RDB$FIELD_LENGTH.NULL = FALSE;
					updateRdbFields(type,
						ARG.RDB$FIELD_TYPE,
						ARG.RDB$FIELD_LENGTH,
						ARG.RDB$FIELD_SUB_TYPE.NULL, ARG.RDB$FIELD_SUB_TYPE,
						ARG.RDB$FIELD_SCALE.NULL, ARG.RDB$FIELD_SCALE,
						ARG.RDB$CHARACTER_SET_ID.NULL, ARG.RDB$CHARACTER_SET_ID,
						ARG.RDB$CHARACTER_LENGTH.NULL, ARG.RDB$CHARACTER_LENGTH,
						ARG.RDB$FIELD_PRECISION.NULL, ARG.RDB$FIELD_PRECISION,
						ARG.RDB$COLLATION_ID.NULL, ARG.RDB$COLLATION_ID,
						segmentLengthNull, segmentLength);
				}
				else
				{
					MetaName fieldName;
					storeGlobalField(tdbb, transaction, fieldName, type);

					ARG.RDB$FIELD_SOURCE.NULL = FALSE;
					strcpy(ARG.RDB$FIELD_SOURCE, fieldName.c_str());
				}
			}
		}
		else
		{
			ARG.RDB$RELATION_NAME.NULL = FALSE;
			strcpy(ARG.RDB$RELATION_NAME, type->typeOfTable.c_str());

			ARG.RDB$FIELD_NAME.NULL = FALSE;
			strcpy(ARG.RDB$FIELD_NAME, type->typeOfName.c_str());

			ARG.RDB$FIELD_SOURCE.NULL = FALSE;
			strcpy(ARG.RDB$FIELD_SOURCE, type->fieldSource.c_str());
		}

		// ASF: If we used a collate with a domain or table.column type, write it
		// into RDB$FUNCTION_ARGUMENTS.

		if (type->collate.hasData() && type->typeOfName.hasData())
		{
			ARG.RDB$COLLATION_ID.NULL = FALSE;
			ARG.RDB$COLLATION_ID = type->collationId;
		}

		// ASF: I moved this block to write defaults on RDB$FUNCTION_ARGUMENTS.
		// It was writing in RDB$FIELDS, but that would require special support
		// for packaged functions signature verification.

		if (parameter->defaultClause)
		{
			if (preserveDefaults)
			{
				status_exception::raise(
					Arg::Gds(isc_dyn_defvaldecl_package_func) <<
					package.c_str() <<
					name.c_str());
			}

			ARG.RDB$DEFAULT_VALUE.NULL = FALSE;
			ARG.RDB$DEFAULT_SOURCE.NULL = FALSE;

			attachment->storeMetaDataBlob(tdbb, transaction, &ARG.RDB$DEFAULT_SOURCE,
				parameter->defaultClause->source);

			dsqlScratch->getBlrData().clear();

			if (dsqlScratch->isVersion4())
				dsqlScratch->appendUChar(blr_version4);
			else
				dsqlScratch->appendUChar(blr_version5);

			GEN_expr(dsqlScratch, parameter->defaultClause->value);

			dsqlScratch->appendUChar(blr_eoc);

			attachment->storeBinaryBlob(tdbb, transaction, &ARG.RDB$DEFAULT_VALUE,
				dsqlScratch->getBlrData());
		}
		else if (preserveDefaults && collectedParameter)
		{
			ARG.RDB$DEFAULT_SOURCE.NULL = collectedParameter->defaultSource.isEmpty();
			if (!ARG.RDB$DEFAULT_SOURCE.NULL)
				ARG.RDB$DEFAULT_SOURCE = collectedParameter->defaultSource;

			ARG.RDB$DEFAULT_VALUE.NULL = collectedParameter->defaultValue.isEmpty();
			if (!ARG.RDB$DEFAULT_VALUE.NULL)
				ARG.RDB$DEFAULT_VALUE = collectedParameter->defaultValue;
		}

		if (isUdf())
		{
			ARG.RDB$MECHANISM.NULL = FALSE;

			if (returnArg &&	// CVC: This is case of "returns <type> [by value|reference]"
				udfReturnPos == 0 && type->dtype == dtype_blob)
			{
				// CVC: I need to test returning blobs by descriptor before allowing the
				// change there. For now, I ignore the return type specification.
				const bool freeIt = parameter->udfMechanism.value < 0;
				ARG.RDB$MECHANISM = (SSHORT) ((freeIt ? -1 : 1) * FUN_blob_struct);
				// If we have the FREE_IT set then the blob has to be freed on return.
			}
			else if (parameter->udfMechanism.specified)
				ARG.RDB$MECHANISM = (SSHORT) parameter->udfMechanism.value;
			else if (type->dtype == dtype_blob)
				ARG.RDB$MECHANISM = (SSHORT) FUN_blob_struct;
			else
				ARG.RDB$MECHANISM = (SSHORT) FUN_reference;
		}

		ARG.RDB$DESCRIPTION.NULL = !collectedParameter || collectedParameter->comment.isEmpty();
		if (!ARG.RDB$DESCRIPTION.NULL)
			ARG.RDB$DESCRIPTION = collectedParameter->comment;
	}
	END_STORE
}

void CreateAlterFunctionNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch)
{
	if (invalid)
		status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_func) << name);

	if (compiled)
		return;

	compiled = true;
	invalid = true;

	if (body)
	{
		dsqlScratch->beginDebug();
		dsqlScratch->getBlrData().clear();

		if (dsqlScratch->isVersion4())
			dsqlScratch->appendUChar(blr_version4);
		else
			dsqlScratch->appendUChar(blr_version5);

		dsqlScratch->appendUChar(blr_begin);

		Array<NestConst<ParameterClause> > returns;
		returns.add(returnType);
		dsqlScratch->genParameters(parameters, returns);

		if (parameters.getCount() != 0)
		{
			dsqlScratch->appendUChar(blr_receive);
			dsqlScratch->appendUChar(0);
		}

		dsqlScratch->appendUChar(blr_begin);

		for (unsigned i = 0; i < parameters.getCount(); ++i)
		{
			ParameterClause* parameter = parameters[i];

			if (parameter->type->fullDomain || parameter->type->notNull)
			{
				// ASF: To validate input parameters we need only to read its value.
				// Assigning it to null is an easy way to do this.
				dsqlScratch->appendUChar(blr_assignment);
				dsqlScratch->appendUChar(blr_parameter2);
				dsqlScratch->appendUChar(0);	// input
				dsqlScratch->appendUShort(i * 2);
				dsqlScratch->appendUShort(i * 2 + 1);
				dsqlScratch->appendUChar(blr_null);
			}
		}

		dsql_var* const variable = dsqlScratch->outputVariables[0];
		dsqlScratch->putLocalVariable(variable, 0, NULL);

		// ASF: This is here to not change the old logic (proc_flag)
		// of previous calls to PASS1_node and PASS1_statement.
		dsqlScratch->setPsql(true);

		dsqlScratch->putLocalVariables(localDeclList, 1);

		dsqlScratch->loopLevel = 0;
		dsqlScratch->cursorNumber = 0;

		StmtNode* stmtNode = body->dsqlPass(dsqlScratch);
		GEN_hidden_variables(dsqlScratch);

		dsqlScratch->appendUChar(blr_stall);
		// put a label before body of procedure,
		// so that any EXIT statement can get out
		dsqlScratch->appendUChar(blr_label);
		dsqlScratch->appendUChar(0);

		stmtNode->genBlr(dsqlScratch);

		dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL);
		dsqlScratch->appendUChar(blr_end);
		dsqlScratch->genReturn(false);
		dsqlScratch->appendUChar(blr_end);
		dsqlScratch->appendUChar(blr_eoc);

		dsqlScratch->endDebug();
	}

	invalid = false;
}

void CreateAlterFunctionNode::collectParameters(thread_db* tdbb, jrd_tra* transaction,
	CollectedParameterMap& items)
{
	AutoRequest requestHandle;

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		ARG IN RDB$FUNCTION_ARGUMENTS
		WITH ARG.RDB$FUNCTION_NAME EQ name.c_str() AND
			 ARG.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
	{
		CollectedParameter parameter;
		parameter.comment = ARG.RDB$DESCRIPTION;
		parameter.defaultSource = ARG.RDB$DEFAULT_SOURCE;
		parameter.defaultValue = ARG.RDB$DEFAULT_VALUE;
		items.put(ARG.RDB$ARGUMENT_NAME, parameter);
	}
	END_FOR
}


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


string AlterExternalFunctionNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, clauses);

	return "AlterExternalFunctionNode";
}

void AlterExternalFunctionNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	SCL_check_function(tdbb, &dscName, SCL_alter);
}

// Allow changing the entry point and/or the module name of a UDF.
void AlterExternalFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	if (clauses.name.isEmpty() && clauses.udfModule.isEmpty())
	{
		status_exception::raise(
			Arg::Gds(isc_sqlerr) << Arg::Num(-104) /*** FIXME: <<
			// Unexpected end of command
			Arg::Gds(isc_command_end_err2) << Arg::Num(node->nod_line) <<
			Arg::Num(node->nod_column + obj_name->str_length)***/);	// + strlen("FUNCTION")
	}

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_m_fun, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FUN IN RDB$FUNCTIONS
		WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND
			 FUN.RDB$PACKAGE_NAME MISSING
	{
		found = true;

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_FUNCTION,
			name, NULL);

		if (!FUN.RDB$ENGINE_NAME.NULL || !FUN.RDB$FUNCTION_BLR.NULL)
			status_exception::raise(Arg::Gds(isc_dyn_newfc_oldsyntax) << name);

		MODIFY FUN
			if (clauses.name.hasData())
			{
				if (clauses.name.length() >= sizeof(FUN.RDB$ENTRYPOINT))
					status_exception::raise(Arg::Gds(isc_dyn_name_longer));

				FUN.RDB$ENTRYPOINT.NULL = FALSE;
				strcpy(FUN.RDB$ENTRYPOINT, clauses.name.c_str());
			}

			if (clauses.udfModule.hasData())
			{
				if (clauses.udfModule.length() >= sizeof(FUN.RDB$MODULE_NAME))
					status_exception::raise(Arg::Gds(isc_dyn_name_longer));

				FUN.RDB$MODULE_NAME.NULL = FALSE;
				strcpy(FUN.RDB$MODULE_NAME, clauses.udfModule.c_str());
			}
		END_MODIFY
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_FUNCTION,
			name, NULL);
	}
	else
	{
		// msg 41: "Function %s not found"
		status_exception::raise(Arg::PrivateDyn(41) << name);
	}

	savePoint.release();	// everything is ok

	// Update DSQL cache
	METD_drop_function(transaction, QualifiedName(name, ""));
	MET_dsql_cache_release(tdbb, SYM_udf, name, "");
}


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


void DropFunctionNode::dropArguments(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& functionName, const MetaName& packageName)
{
	AutoCacheRequest requestHandle(tdbb, drq_e_func_args, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		ARG IN RDB$FUNCTION_ARGUMENTS
		WITH ARG.RDB$FUNCTION_NAME EQ functionName.c_str() AND
			 ARG.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
	{
		// get rid of arguments in rdb$fields
		if (!ARG.RDB$FIELD_SOURCE.NULL && ARG.RDB$RELATION_NAME.NULL && ARG.RDB$FIELD_NAME.NULL)
		{
			AutoCacheRequest requestHandle2(tdbb, drq_e_arg_gfld, DYN_REQUESTS);

			FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
				FLD IN RDB$FIELDS
				WITH FLD.RDB$FIELD_NAME EQ ARG.RDB$FIELD_SOURCE AND
					 FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND
					 FLD.RDB$SYSTEM_FLAG EQ 0
			{
				if (!FLD.RDB$SECURITY_CLASS.NULL)
					deleteSecurityClass(tdbb, transaction, FLD.RDB$SECURITY_CLASS);

				AutoCacheRequest request3(tdbb, drq_e_arg_prvs, DYN_REQUESTS);

				FOR(REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
					PRIV IN RDB$USER_PRIVILEGES
					WITH PRIV.RDB$RELATION_NAME EQ FLD.RDB$FIELD_NAME AND
					PRIV.RDB$OBJECT_TYPE = obj_field AND
					PRIV.RDB$GRANTOR NOT MISSING
				{
					ERASE PRIV;
				}
				END_FOR

				ERASE FLD;
			}
			END_FOR
		}

		ERASE ARG;
	}
	END_FOR
}

string DropFunctionNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, silent);
	NODE_PRINT(printer, package);

	return "DropFunctionNode";
}

DdlNode* DropFunctionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_FUNCTION);
	return DdlNode::dsqlPass(dsqlScratch);
}

void DropFunctionNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	SCL_check_function(tdbb, &dscName, SCL_drop);
}

void DropFunctionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);
	bool found = false;

	dropArguments(tdbb, transaction, name, package);

	AutoCacheRequest requestHandle(tdbb, drq_e_funcs, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		FUN IN RDB$FUNCTIONS
		WITH FUN.RDB$FUNCTION_NAME EQ name.c_str() AND
			 FUN.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
	{
		if (FUN.RDB$SYSTEM_FLAG)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_cannot_mod_sysfunc) <<
				MetaName(FUN.RDB$FUNCTION_NAME));
		}

		if (package.isEmpty())
		{
			executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_FUNCTION,
				name, NULL);
		}

		ERASE FUN;

		if (!FUN.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, FUN.RDB$SECURITY_CLASS);

		found = true;
	}
	END_FOR

	if (!found && !silent)
		status_exception::raise(Arg::Gds(isc_dyn_func_not_found) << Arg::Str(name));

	if (package.isEmpty())
	{
		requestHandle.reset(tdbb, drq_e_fun_prvs, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str()
				AND PRIV.RDB$OBJECT_TYPE = obj_udf
				AND PRIV.RDB$GRANTOR NOT MISSING
		{
			ERASE PRIV;
		}
		END_FOR

		requestHandle.reset(tdbb, drq_e_fun_prv, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str()
				AND PRIV.RDB$USER_TYPE = obj_udf
				AND PRIV.RDB$GRANTOR NOT MISSING
		{
			ERASE PRIV;
		}
		END_FOR
	}

	if (found && package.isEmpty())
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_FUNCTION,
			name, NULL);
	}

	savePoint.release();	// everything is ok

	// Update DSQL cache
	METD_drop_function(transaction, QualifiedName(name, package));
	MET_dsql_cache_release(tdbb, SYM_udf, name, package);
}


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


string CreateAlterProcedureNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, create);
	NODE_PRINT(printer, alter);
	NODE_PRINT(printer, external);
	NODE_PRINT(printer, parameters);
	NODE_PRINT(printer, returns);
	NODE_PRINT(printer, source);
	NODE_PRINT(printer, localDeclList);
	NODE_PRINT(printer, body);
	NODE_PRINT(printer, compiled);
	NODE_PRINT(printer, invalid);
	NODE_PRINT(printer, package);
	NODE_PRINT(printer, packageOwner);
	NODE_PRINT(printer, privateScope);

	return "CreateAlterProcedureNode";
}

DdlNode* CreateAlterProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE);

	// check for duplicated parameters and declaration names

	StrArray names;

	for (FB_SIZE_T i = 0; i < parameters.getCount(); ++i)
	{
		const ParameterClause* const parameter = parameters[i];

		if (names.exist(parameter->name.c_str()))
		{
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
				Arg::Gds(isc_dsql_duplicate_spec) << Arg::Str(parameter->name));
		}

		names.add(parameter->name.c_str());
	}

	for (FB_SIZE_T i = 0; i < returns.getCount(); ++i)
	{
		const ParameterClause* const parameter = returns[i];

		if (names.exist(parameter->name.c_str()))
		{
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
				Arg::Gds(isc_dsql_duplicate_spec) << Arg::Str(parameter->name));
		}

		names.add(parameter->name.c_str());
	}

	PASS1_check_unique_fields_names(names, localDeclList);

	source.ltrim("\n\r\t ");

	bool hasDefaultParams = false;

	// compile default expressions
	for (unsigned i = 0; i < parameters.getCount(); ++i)
	{
		ParameterClause* parameter = parameters[i];

		if (parameter->defaultClause)
		{
			hasDefaultParams = true;
			parameter->defaultClause->value = doDsqlPass(dsqlScratch, parameter->defaultClause->value);
		}
		else if (hasDefaultParams)
		{
			// parameter without default value after parameters with default
			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
					  Arg::Gds(isc_bad_default_value) <<
					  Arg::Gds(isc_invalid_clause) << Arg::Str("defaults must be last"));
		}
	}

	for (unsigned i = 0; i < parameters.getCount(); ++i)
		parameters[i]->type->resolve(dsqlScratch);

	for (unsigned i = 0; i < returns.getCount(); ++i)
		returns[i]->type->resolve(dsqlScratch);

	// check SQL SECURITY is not set if procedure declared in package
	if (package.hasData() && ssDefiner.specified)
	{
		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
				  Arg::Gds(isc_invalid_clause) << Arg::Str("SQL SECURITY for procedures is prohibit in packages"));
	}

	return DdlNode::dsqlPass(dsqlScratch);
}

void CreateAlterProcedureNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	if (alter)
	{
		if (SCL_check_procedure(tdbb, &dscName, SCL_alter) || !create)
			return;
	}

	SCL_check_create_access(tdbb, SCL_object_procedure);
}

void CreateAlterProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	fb_assert(create || alter);

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);
	bool altered = false;

	// first pass
	if (alter)
	{
		if (executeAlter(tdbb, dsqlScratch, transaction, false, true))
			altered = true;
		else
		{
			if (create)	// create or alter
				executeCreate(tdbb, dsqlScratch, transaction);
			else
				status_exception::raise(Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name));
		}
	}
	else
		executeCreate(tdbb, dsqlScratch, transaction);

	compile(tdbb, dsqlScratch);

	executeAlter(tdbb, dsqlScratch, transaction, true, false);	// second pass

	if (package.isEmpty())
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
			(altered ? DDL_TRIGGER_ALTER_PROCEDURE : DDL_TRIGGER_CREATE_PROCEDURE), name, NULL);
	}

	savePoint.release();	// everything is ok

	if (alter)
	{
		// Update DSQL cache
		METD_drop_procedure(transaction, QualifiedName(name, package));
		MET_dsql_cache_release(tdbb, SYM_procedure, name, package);
	}
}

void CreateAlterProcedureNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->getAttachment();
	const MetaString& ownerName = attachment->getEffectiveUserName();

	if (package.isEmpty())
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_CREATE_PROCEDURE, name, NULL);

		DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_procedure);
	}

	AutoCacheRequest requestHandle(tdbb, drq_s_prcs2, DYN_REQUESTS);

	int faults = 0;

	while (true)
	{
		try
		{
			SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_prc_id, "RDB$PROCEDURES");
			id %= (MAX_SSHORT + 1);

			if (!id)
				continue;

			STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
				P IN RDB$PROCEDURES
			{
				P.RDB$PROCEDURE_ID = id;
				P.RDB$SYSTEM_FLAG = 0;
				strcpy(P.RDB$PROCEDURE_NAME, name.c_str());

				if (package.hasData())
				{
					P.RDB$PACKAGE_NAME.NULL = FALSE;
					strcpy(P.RDB$PACKAGE_NAME, package.c_str());

					P.RDB$PRIVATE_FLAG.NULL = FALSE;
					P.RDB$PRIVATE_FLAG = privateScope;

					strcpy(P.RDB$OWNER_NAME, packageOwner.c_str());
				}
				else
				{
					P.RDB$PACKAGE_NAME.NULL = TRUE;
					P.RDB$PRIVATE_FLAG.NULL = TRUE;

					strcpy(P.RDB$OWNER_NAME, ownerName.c_str());
				}
			}
			END_STORE

			break;
		}
		catch (const status_exception& ex)
		{
			if (ex.value()[1] != isc_unique_key_violation)
				throw;

			if (++faults > MAX_SSHORT)
				throw;

			fb_utils::init_status(tdbb->tdbb_status_vector);
		}
	}

	if (package.isEmpty())
		storePrivileges(tdbb, transaction, name, obj_procedure, EXEC_PRIVILEGES);

	executeAlter(tdbb, dsqlScratch, transaction, false, false);
}

bool CreateAlterProcedureNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, bool secondPass, bool runTriggers)
{
	Attachment* const attachment = transaction->getAttachment();
	AutoCacheRequest requestHandle(tdbb, drq_m_prcs2, DYN_REQUESTS);
	bool modified = false;

	DsqlCompiledStatement* statement = dsqlScratch->getStatement();

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		P IN RDB$PROCEDURES
		WITH P.RDB$PROCEDURE_NAME EQ name.c_str() AND
			 P.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
	{
		if (P.RDB$SYSTEM_FLAG)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_cannot_mod_sysproc) <<
				MetaName(P.RDB$PROCEDURE_NAME));
		}

		if (!secondPass && runTriggers && package.isEmpty())
		{
			executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
				DDL_TRIGGER_ALTER_PROCEDURE, name, NULL);
		}

		MODIFY P
			if (secondPass)
			{
				P.RDB$PROCEDURE_BLR.NULL = TRUE;
				P.RDB$DEBUG_INFO.NULL = TRUE;
				P.RDB$PROCEDURE_TYPE.NULL = TRUE;

				P.RDB$PROCEDURE_INPUTS = (USHORT) parameters.getCount();
				P.RDB$PROCEDURE_OUTPUTS = (USHORT) returns.getCount();
			}
			else
			{
				P.RDB$ENGINE_NAME.NULL = TRUE;
				P.RDB$ENTRYPOINT.NULL = TRUE;
				P.RDB$PROCEDURE_SOURCE.NULL = TRUE;
				P.RDB$VALID_BLR.NULL = TRUE;

				P.RDB$PROCEDURE_SOURCE.NULL = !(source.hasData() && (external || package.isEmpty()));
				if (!P.RDB$PROCEDURE_SOURCE.NULL)
					attachment->storeMetaDataBlob(tdbb, transaction, &P.RDB$PROCEDURE_SOURCE, source);

				if (package.hasData())
				{
					P.RDB$PRIVATE_FLAG.NULL = FALSE;
					P.RDB$PRIVATE_FLAG = privateScope;
				}
				else
					P.RDB$PRIVATE_FLAG.NULL = TRUE;

				if (ssDefiner.specified)
				{
					P.RDB$SQL_SECURITY.NULL = FALSE;
					P.RDB$SQL_SECURITY = ssDefiner.value ? FB_TRUE : FB_FALSE;
				}
				else
					P.RDB$SQL_SECURITY.NULL = TRUE;
			}

			if (external)
			{
				if (secondPass)
				{
					P.RDB$PROCEDURE_TYPE.NULL = FALSE;
					P.RDB$PROCEDURE_TYPE = (USHORT) prc_selectable;
				}
				else
				{
					P.RDB$ENGINE_NAME.NULL = FALSE;
					strcpy(P.RDB$ENGINE_NAME, external->engine.c_str());

					if (external->name.length() >= sizeof(P.RDB$ENTRYPOINT))
						status_exception::raise(Arg::Gds(isc_dyn_name_longer));

					P.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
					strcpy(P.RDB$ENTRYPOINT, external->name.c_str());
				}
			}
			else if (body)
			{
				if (secondPass)
				{
					P.RDB$VALID_BLR.NULL = FALSE;
					P.RDB$VALID_BLR = TRUE;

					P.RDB$PROCEDURE_BLR.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$PROCEDURE_BLR,
						dsqlScratch->getBlrData());

					P.RDB$DEBUG_INFO.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction, &P.RDB$DEBUG_INFO,
						dsqlScratch->getDebugData());

					P.RDB$PROCEDURE_TYPE.NULL = FALSE;
					P.RDB$PROCEDURE_TYPE = (USHORT)
						(statement->getFlags() & DsqlCompiledStatement::FLAG_SELECTABLE ?
							prc_selectable : prc_executable);
				}
			}

			modified = true;
		END_MODIFY
	}
	END_FOR

	if (!secondPass && modified)
	{
		// Get all comments and defaults from the old parameter list.
		CollectedParameterMap collectedParameters;
		collectParameters(tdbb, transaction, collectedParameters);

		// Delete all old input and output parameters.
		DropProcedureNode::dropParameters(tdbb, transaction, name, package);

		// And insert the new ones.

		for (FB_SIZE_T i = 0; i < parameters.getCount(); ++i)
		{
			ParameterClause* parameter = parameters[i];
			CollectedParameter collectedParameter;

			// Find the original parameter to recreate in the new parameter.
			collectedParameters.get(parameter->name, collectedParameter);

			storeParameter(tdbb, dsqlScratch, transaction, 0, i, parameter, &collectedParameter);
		}

		for (FB_SIZE_T i = 0; i < returns.getCount(); ++i)
		{
			ParameterClause* parameter = returns[i];
			CollectedParameter collectedParameter;

			// Find the original parameter to recreate in the new parameter.
			collectedParameters.get(parameter->name, collectedParameter);

			storeParameter(tdbb, dsqlScratch, transaction, 1, i, parameter, &collectedParameter);
		}

		AutoCacheRequest requestHandle2(tdbb, drq_m_prm_view, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
			PRM IN RDB$PROCEDURE_PARAMETERS CROSS
			RFR IN RDB$RELATION_FIELDS CROSS
			VRL IN RDB$VIEW_RELATIONS
			WITH PRM.RDB$PROCEDURE_NAME EQ name.c_str() AND
				 PRM.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '') AND
				 VRL.RDB$RELATION_NAME EQ PRM.RDB$PROCEDURE_NAME AND
				 VRL.RDB$PACKAGE_NAME EQUIV PRM.RDB$PACKAGE_NAME AND
				 VRL.RDB$CONTEXT_TYPE EQ VCT_PROCEDURE AND
				 RFR.RDB$RELATION_NAME EQ VRL.RDB$VIEW_NAME AND
				 RFR.RDB$VIEW_CONTEXT EQ VRL.RDB$VIEW_CONTEXT AND
				 RFR.RDB$BASE_FIELD = PRM.RDB$PARAMETER_NAME
		{
			MODIFY RFR
			{
				strcpy(RFR.RDB$FIELD_SOURCE, PRM.RDB$FIELD_SOURCE);
			}
			END_MODIFY
		}
		END_FOR
	}

	return modified;
}

void CreateAlterProcedureNode::storeParameter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, USHORT parameterType, unsigned pos, ParameterClause* parameter,
	const CollectedParameter* collectedParameter)
{
	Attachment* const attachment = transaction->getAttachment();
	TypeClause* type = parameter->type;

	AutoCacheRequest requestHandle(tdbb, drq_s_prms4, DYN_REQUESTS);

	STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PRM IN RDB$PROCEDURE_PARAMETERS
	{
		PRM.RDB$PARAMETER_NAME.NULL = FALSE;
		strcpy(PRM.RDB$PARAMETER_NAME, parameter->name.c_str());

		PRM.RDB$PROCEDURE_NAME.NULL = FALSE;
		strcpy(PRM.RDB$PROCEDURE_NAME, name.c_str());

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

		PRM.RDB$SYSTEM_FLAG = 0;
		PRM.RDB$SYSTEM_FLAG.NULL = FALSE; // Probably superflous because the field is not nullable

		PRM.RDB$PARAMETER_NUMBER.NULL = FALSE;
		PRM.RDB$PARAMETER_NUMBER = pos;

		PRM.RDB$PARAMETER_TYPE.NULL = FALSE;
		PRM.RDB$PARAMETER_TYPE = parameterType;

		PRM.RDB$PARAMETER_MECHANISM.NULL = FALSE;
		PRM.RDB$PARAMETER_MECHANISM = (USHORT) (type->fullDomain || type->typeOfName.isEmpty() ?
			prm_mech_normal : prm_mech_type_of);

		PRM.RDB$NULL_FLAG.NULL = !type->notNull;
		PRM.RDB$NULL_FLAG = type->notNull;

		PRM.RDB$RELATION_NAME.NULL = type->typeOfTable.isEmpty();
		PRM.RDB$FIELD_NAME.NULL = PRM.RDB$RELATION_NAME.NULL || type->typeOfName.isEmpty();

		PRM.RDB$FIELD_SOURCE.NULL = FALSE;

		if (PRM.RDB$RELATION_NAME.NULL)
		{
			if (type->typeOfName.hasData())
				strcpy(PRM.RDB$FIELD_SOURCE, type->typeOfName.c_str());
			else
			{
				MetaName fieldName;
				storeGlobalField(tdbb, transaction, fieldName, type);
				strcpy(PRM.RDB$FIELD_SOURCE, fieldName.c_str());
			}
		}
		else
		{
			strcpy(PRM.RDB$RELATION_NAME, type->typeOfTable.c_str());
			strcpy(PRM.RDB$FIELD_NAME, type->typeOfName.c_str());
			strcpy(PRM.RDB$FIELD_SOURCE, type->fieldSource.c_str());
		}

		// ASF: If we used a collate with a domain or table.column type, write it
		// in RDB$PROCEDURE_PARAMETERS.

		PRM.RDB$COLLATION_ID.NULL = !(type->collate.hasData() && type->typeOfName.hasData());

		if (!PRM.RDB$COLLATION_ID.NULL)
			PRM.RDB$COLLATION_ID = type->collationId;

		// ASF: I moved this block to write defaults on RDB$PROCEDURE_PARAMETERS.
		// It was writing in RDB$FIELDS, but that would require special support
		// for packaged procedures signature verification.

		PRM.RDB$DEFAULT_VALUE.NULL = !parameter->defaultClause;
		PRM.RDB$DEFAULT_SOURCE.NULL = !parameter->defaultClause;

		if (parameter->defaultClause)
		{
			if (preserveDefaults)
			{
				status_exception::raise(
					Arg::Gds(isc_dyn_defvaldecl_package_proc) <<
					package.c_str() <<
					name.c_str());
			}

			attachment->storeMetaDataBlob(tdbb, transaction, &PRM.RDB$DEFAULT_SOURCE,
				parameter->defaultClause->source);

			dsqlScratch->getBlrData().clear();

			if (dsqlScratch->isVersion4())
				dsqlScratch->appendUChar(blr_version4);
			else
				dsqlScratch->appendUChar(blr_version5);

			GEN_expr(dsqlScratch, parameter->defaultClause->value);

			dsqlScratch->appendUChar(blr_eoc);

			attachment->storeBinaryBlob(tdbb, transaction, &PRM.RDB$DEFAULT_VALUE,
				dsqlScratch->getBlrData());
		}
		else if (preserveDefaults && collectedParameter)
		{
			PRM.RDB$DEFAULT_SOURCE.NULL = collectedParameter->defaultSource.isEmpty();
			if (!PRM.RDB$DEFAULT_SOURCE.NULL)
				PRM.RDB$DEFAULT_SOURCE = collectedParameter->defaultSource;

			PRM.RDB$DEFAULT_VALUE.NULL = collectedParameter->defaultValue.isEmpty();
			if (!PRM.RDB$DEFAULT_VALUE.NULL)
				PRM.RDB$DEFAULT_VALUE = collectedParameter->defaultValue;
		}

		PRM.RDB$DESCRIPTION.NULL = !collectedParameter || collectedParameter->comment.isEmpty();
		if (!PRM.RDB$DESCRIPTION.NULL)
			PRM.RDB$DESCRIPTION = collectedParameter->comment;
	}
	END_STORE
}

void CreateAlterProcedureNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch)
{
	if (invalid)
		status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_proc) << name);

	if (compiled)
		return;

	compiled = true;

	if (!body)
		return;

	invalid = true;

	dsqlScratch->beginDebug();
	dsqlScratch->getBlrData().clear();

	if (dsqlScratch->isVersion4())
		dsqlScratch->appendUChar(blr_version4);
	else
		dsqlScratch->appendUChar(blr_version5);

	dsqlScratch->appendUChar(blr_begin);

	dsqlScratch->genParameters(parameters, returns);

	if (parameters.getCount() != 0)
	{
		dsqlScratch->appendUChar(blr_receive);
		dsqlScratch->appendUChar(0);
	}

	dsqlScratch->appendUChar(blr_begin);

	for (unsigned i = 0; i < parameters.getCount(); ++i)
	{
		ParameterClause* parameter = parameters[i];

		if (parameter->type->fullDomain || parameter->type->notNull)
		{
			// ASF: To validate an input parameter we need only to read its value.
			// Assigning it to null is an easy way to do this.
			dsqlScratch->appendUChar(blr_assignment);
			dsqlScratch->appendUChar(blr_parameter2);
			dsqlScratch->appendUChar(0);	// input
			dsqlScratch->appendUShort(i * 2);
			dsqlScratch->appendUShort(i * 2 + 1);
			dsqlScratch->appendUChar(blr_null);
		}
	}

	for (Array<dsql_var*>::const_iterator i = dsqlScratch->outputVariables.begin();
		 i != dsqlScratch->outputVariables.end();
		 ++i)
	{
		dsqlScratch->putLocalVariable(*i, 0, NULL);
	}

	// ASF: This is here to not change the old logic (proc_flag)
	// of previous calls to PASS1_node and PASS1_statement.
	dsqlScratch->setPsql(true);

	dsqlScratch->putLocalVariables(localDeclList, returns.getCount());

	dsqlScratch->loopLevel = 0;
	dsqlScratch->cursorNumber = 0;

	StmtNode* stmtNode = body->dsqlPass(dsqlScratch);
	GEN_hidden_variables(dsqlScratch);

	dsqlScratch->appendUChar(blr_stall);
	// put a label before body of procedure,
	// so that any EXIT statement can get out
	dsqlScratch->appendUChar(blr_label);
	dsqlScratch->appendUChar(0);

	stmtNode->genBlr(dsqlScratch);

	dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL);
	dsqlScratch->appendUChar(blr_end);
	dsqlScratch->genReturn(true);
	dsqlScratch->appendUChar(blr_end);
	dsqlScratch->appendUChar(blr_eoc);

	dsqlScratch->endDebug();

	invalid = false;
}

void CreateAlterProcedureNode::collectParameters(thread_db* tdbb, jrd_tra* transaction,
	CollectedParameterMap& items)
{
	AutoRequest requestHandle;

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PRM IN RDB$PROCEDURE_PARAMETERS
		WITH PRM.RDB$PROCEDURE_NAME EQ name.c_str() AND
			 PRM.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
	{
		CollectedParameter parameter;
		parameter.comment = PRM.RDB$DESCRIPTION;
		parameter.defaultSource = PRM.RDB$DEFAULT_SOURCE;
		parameter.defaultValue = PRM.RDB$DEFAULT_VALUE;
		items.put(PRM.RDB$PARAMETER_NAME, parameter);
	}
	END_FOR
}


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


void DropProcedureNode::dropParameters(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& procedureName, const MetaName& packageName)
{
	AutoCacheRequest requestHandle(tdbb, drq_e_prms2, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PRM IN RDB$PROCEDURE_PARAMETERS
		WITH PRM.RDB$PROCEDURE_NAME EQ procedureName.c_str() AND
			 PRM.RDB$PACKAGE_NAME EQUIV NULLIF(packageName.c_str(), '')
	{
		// get rid of parameters in rdb$fields
		if (!PRM.RDB$FIELD_SOURCE.NULL && PRM.RDB$RELATION_NAME.NULL && PRM.RDB$FIELD_NAME.NULL)
		{
			AutoCacheRequest requestHandle2(tdbb, drq_e_prm_gfld, DYN_REQUESTS);

			FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
				FLD IN RDB$FIELDS
				WITH FLD.RDB$FIELD_NAME EQ PRM.RDB$FIELD_SOURCE AND
					 FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND
					 FLD.RDB$SYSTEM_FLAG EQ 0
			{
				if (!FLD.RDB$SECURITY_CLASS.NULL)
					deleteSecurityClass(tdbb, transaction, FLD.RDB$SECURITY_CLASS);

				AutoCacheRequest request3(tdbb, drq_e_prm_prvs, DYN_REQUESTS);

				FOR(REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
					PRIV IN RDB$USER_PRIVILEGES
					WITH PRIV.RDB$RELATION_NAME EQ FLD.RDB$FIELD_NAME AND
					PRIV.RDB$OBJECT_TYPE = obj_field AND
					PRIV.RDB$GRANTOR NOT MISSING
				{
					ERASE PRIV;
				}
				END_FOR

				ERASE FLD;
			}
			END_FOR
		}

		ERASE PRM;
	}
	END_FOR
}

string DropProcedureNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, silent);
	NODE_PRINT(printer, package);

	return "DropProcedureNode";
}

DdlNode* DropProcedureNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_PROCEDURE);
	return DdlNode::dsqlPass(dsqlScratch);
}

void DropProcedureNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	SCL_check_procedure(tdbb, &dscName, SCL_drop);
}

void DropProcedureNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);
	bool found = false;

	dropParameters(tdbb, transaction, name, package);

	AutoCacheRequest requestHandle(tdbb, drq_e_prcs2, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PRC IN RDB$PROCEDURES
		WITH PRC.RDB$PROCEDURE_NAME EQ name.c_str() AND
			 PRC.RDB$PACKAGE_NAME EQUIV NULLIF(package.c_str(), '')
	{
		if (PRC.RDB$SYSTEM_FLAG)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_cannot_mod_sysproc) <<
				MetaName(PRC.RDB$PROCEDURE_NAME));
		}

		if (package.isEmpty())
		{
			executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
				DDL_TRIGGER_DROP_PROCEDURE, name, NULL);
		}

		ERASE PRC;

		if (!PRC.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, PRC.RDB$SECURITY_CLASS);

		found = true;
	}
	END_FOR

	if (!found && !silent)
		status_exception::raise(Arg::Gds(isc_dyn_proc_not_found) << Arg::Str(name));

	if (package.isEmpty())
	{
		requestHandle.reset(tdbb, drq_e_prc_prvs, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$RELATION_NAME EQ name.c_str()
				AND PRIV.RDB$OBJECT_TYPE = obj_procedure
				AND PRIV.RDB$GRANTOR NOT MISSING
		{
			ERASE PRIV;
		}
		END_FOR

		requestHandle.reset(tdbb, drq_e_prc_prv, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			PRIV IN RDB$USER_PRIVILEGES WITH PRIV.RDB$USER EQ name.c_str()
				AND PRIV.RDB$USER_TYPE = obj_procedure
				AND PRIV.RDB$GRANTOR NOT MISSING
		{
			ERASE PRIV;
		}
		END_FOR
	}

	if (found && package.isEmpty())
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_PROCEDURE,
			name, NULL);
	}

	savePoint.release();	// everything is ok

	// Update DSQL cache
	METD_drop_procedure(transaction, QualifiedName(name, package));
	MET_dsql_cache_release(tdbb, SYM_procedure, name, package);
}


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


void TriggerDefinition::store(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	if (name.isEmpty())
		DYN_UTIL_generate_trigger_name(tdbb, transaction, name);

	AutoCacheRequest requestHandle(tdbb, drq_s_triggers, DYN_REQUESTS);

	STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		TRG IN RDB$TRIGGERS
	{
		TRG.RDB$SYSTEM_FLAG = SSHORT(systemFlag);
		TRG.RDB$FLAGS = TRG_sql | (fkTrigger ? TRG_ignore_perm : 0);
		strcpy(TRG.RDB$TRIGGER_NAME, name.c_str());

		TRG.RDB$RELATION_NAME.NULL = relationName.isEmpty();
		strcpy(TRG.RDB$RELATION_NAME, relationName.c_str());

		fb_assert(type.specified);
		TRG.RDB$TRIGGER_TYPE = type.value;

		TRG.RDB$TRIGGER_SEQUENCE = (!position.specified ? 0 : position.value);
		TRG.RDB$TRIGGER_INACTIVE = (!active.specified ? 0 : (USHORT) !active.value);
	}
	END_STORE

	modify(tdbb, dsqlScratch, transaction);
}

bool TriggerDefinition::modify(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->getAttachment();
	bool modified = false;

	AutoCacheRequest requestHandle(tdbb, drq_m_trigger2, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		TRG IN RDB$TRIGGERS
		WITH TRG.RDB$TRIGGER_NAME EQ name.c_str()
	{
		if (type.specified && type.value != (FB_UINT64) TRG.RDB$TRIGGER_TYPE &&
			TRG.RDB$RELATION_NAME.NULL)
		{
			status_exception::raise(
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_dsql_db_trigger_type_cant_change));
		}

		if (systemFlag == fb_sysflag_user)
		{
			switch (TRG.RDB$SYSTEM_FLAG)
			{
				case fb_sysflag_check_constraint:
				case fb_sysflag_referential_constraint:
				case fb_sysflag_view_check:
					status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig));
					break;

				case fb_sysflag_system:
					status_exception::raise(
						Arg::Gds(isc_dyn_cannot_mod_systrig) << MetaName(TRG.RDB$TRIGGER_NAME));
					break;

				default:
					break;
			}
		}

		preModify(tdbb, dsqlScratch, transaction);

		MODIFY TRG
			if (blrData.length > 0 || external)
			{
				fb_assert(!(blrData.length > 0 && external));

				TRG.RDB$ENGINE_NAME.NULL = TRUE;
				TRG.RDB$ENTRYPOINT.NULL = TRUE;
				TRG.RDB$TRIGGER_SOURCE.NULL = TRUE;
				TRG.RDB$TRIGGER_BLR.NULL = TRUE;
				TRG.RDB$DEBUG_INFO.NULL = TRUE;
				TRG.RDB$VALID_BLR.NULL = TRUE;
			}

			TRG.RDB$RELATION_NAME.NULL = relationName.isEmpty();
			if (relationName.hasData())
				strcpy(TRG.RDB$RELATION_NAME, relationName.c_str());

			if (type.specified)
				TRG.RDB$TRIGGER_TYPE = type.value;

			if (position.specified)
				TRG.RDB$TRIGGER_SEQUENCE = position.value;

			if (active.specified)
				TRG.RDB$TRIGGER_INACTIVE = (USHORT) !active.value;

			if (external)
			{
				TRG.RDB$ENGINE_NAME.NULL = FALSE;
				strcpy(TRG.RDB$ENGINE_NAME, external->engine.c_str());

				if (external->name.length() >= sizeof(TRG.RDB$ENTRYPOINT))
					status_exception::raise(Arg::Gds(isc_dyn_name_longer));

				TRG.RDB$ENTRYPOINT.NULL = (SSHORT) external->name.isEmpty();
				strcpy(TRG.RDB$ENTRYPOINT, external->name.c_str());
			}
			else if (blrData.length > 0)
			{
				TRG.RDB$VALID_BLR.NULL = FALSE;
				TRG.RDB$VALID_BLR = TRUE;

				TRG.RDB$TRIGGER_BLR.NULL = FALSE;
				attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$TRIGGER_BLR, blrData);
			}

			if (debugData.length > 0)
			{
				TRG.RDB$DEBUG_INFO.NULL = FALSE;
				attachment->storeBinaryBlob(tdbb, transaction, &TRG.RDB$DEBUG_INFO, debugData);
			}

			if (source.hasData())
			{
				TRG.RDB$TRIGGER_SOURCE.NULL = FALSE;
				attachment->storeMetaDataBlob(tdbb, transaction, &TRG.RDB$TRIGGER_SOURCE, source);
			}

			if (ssDefiner.specified)
			{
				if (ssDefiner.value != SS_DROP)
				{
					TRG.RDB$SQL_SECURITY.NULL = FALSE;
					TRG.RDB$SQL_SECURITY = ssDefiner.value == SS_DEFINER ? FB_TRUE : FB_FALSE;
				}
				else
					TRG.RDB$SQL_SECURITY.NULL = TRUE;
			}

			modified = true;
		END_MODIFY
	}
	END_FOR

	if (modified)
		postModify(tdbb, dsqlScratch, transaction);

	return modified;
}


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


string CreateAlterTriggerNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, create);
	NODE_PRINT(printer, alter);
	NODE_PRINT(printer, localDeclList);
	NODE_PRINT(printer, body);
	NODE_PRINT(printer, compiled);
	NODE_PRINT(printer, invalid);

	return "CreateAlterTriggerNode";
}

DdlNode* CreateAlterTriggerNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER);

	if (type.specified)
	{
		if (create &&	// ALTER TRIGGER doesn't accept table name
			((relationName.hasData() &&
				(type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DML) ||
			 (relationName.isEmpty() &&
				(type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DB &&
				(type.value & (unsigned) TRIGGER_TYPE_MASK) != (unsigned) TRIGGER_TYPE_DDL)))
		{
			status_exception::raise(
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_dsql_incompatible_trigger_type));
		}
	}

	if (create && ssDefiner.specified && ssDefiner.value == TriggerDefinition::SS_DROP)
		status_exception::raise(Arg::Gds(isc_dsql_command_err) << Arg::Gds(isc_dsql_invalid_drop_ss_clause));

	return DdlNode::dsqlPass(dsqlScratch);
}

void CreateAlterTriggerNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	if (!create)
	{
		AutoRequest requestHandle;

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			TRG IN RDB$TRIGGERS
			WITH TRG.RDB$TRIGGER_NAME EQ name.c_str()
		{
			if (!type.specified && !TRG.RDB$TRIGGER_TYPE.NULL)
				type = TRG.RDB$TRIGGER_TYPE;

			if (relationName.isEmpty() && !TRG.RDB$RELATION_NAME.NULL)
				relationName = TRG.RDB$RELATION_NAME;
		}
		END_FOR

		if (!type.specified)
			status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));
	}

	if (relationName.hasData())
	{
		dsc dscName;
		dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());
		SCL_check_relation(tdbb, &dscName, SCL_alter);
	}
	else
		SCL_check_database(tdbb, SCL_alter);
}

void CreateAlterTriggerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	fb_assert(create || alter);

	Attachment* const attachment = transaction->getAttachment();

	source.ltrim("\n\r\t ");

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	compile(tdbb, dsqlScratch);

	blrData = dsqlScratch->getBlrData();
	debugData = dsqlScratch->getDebugData();

	if (alter)
	{
		if (!modify(tdbb, dsqlScratch, transaction))
		{
			if (create)	// create or alter
				executeCreate(tdbb, dsqlScratch, transaction);
			else
				status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));
		}
	}
	else
		executeCreate(tdbb, dsqlScratch, transaction);

	savePoint.release();	// everything is ok
}

void CreateAlterTriggerNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TRIGGER,
		name, NULL);

	store(tdbb, dsqlScratch, transaction);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TRIGGER,
		name, NULL);
}

void CreateAlterTriggerNode::compile(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch)
{
	if (invalid)
		status_exception::raise(Arg::Gds(isc_dyn_invalid_ddl_trig) << name);

	if (compiled)
		return;

	compiled = true;
	invalid = true;

	if (body)
	{
		dsqlScratch->beginDebug();
		dsqlScratch->getBlrData().clear();

		// Create the "OLD" and "NEW" contexts for the trigger --
		// the new one could be a dummy place holder to avoid resolving
		// fields to that context but prevent relations referenced in
		// the trigger actions from referencing the predefined "1" context.
		if (dsqlScratch->contextNumber)
			dsqlScratch->resetTriggerContextStack();

		if (relationName.hasData())
		{
			RelationSourceNode* relationNode = FB_NEW_POOL(dsqlScratch->getPool()) RelationSourceNode(
				dsqlScratch->getPool(), relationName);

			const string temp = relationNode->alias;	// always empty?

			if (hasOldContext(type.value))
			{
				relationNode->alias = OLD_CONTEXT_NAME;
				dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, relationNode);
				oldContext->ctx_flags |= CTX_system | CTX_cursor;
			}
			else
				dsqlScratch->contextNumber++;

			if (hasNewContext(type.value))
			{
				relationNode->alias = NEW_CONTEXT_NAME;
				dsql_ctx* newContext = PASS1_make_context(dsqlScratch, relationNode);
				newContext->ctx_flags |= CTX_system | CTX_cursor;
			}
			else
				dsqlScratch->contextNumber++;

			relationNode->alias = temp;
		}

		// generate the trigger blr

		if (dsqlScratch->isVersion4())
			dsqlScratch->appendUChar(blr_version4);
		else
			dsqlScratch->appendUChar(blr_version5);

		dsqlScratch->appendUChar(blr_begin);

		dsqlScratch->setPsql(true);
		dsqlScratch->putLocalVariables(localDeclList, 0);

		dsqlScratch->loopLevel = 0;
		dsqlScratch->cursorNumber = 0;
		dsqlScratch->scopeLevel++;

		StmtNode* stmtNode = body->dsqlPass(dsqlScratch);
		GEN_hidden_variables(dsqlScratch);

		// dimitr: I see no reason to deny EXIT command in triggers,
		// hence I've added zero label at the beginning.
		// My first suspicion regarding an obvious conflict
		// with trigger messages (nod_abort) is wrong,
		// although the fact that they use the same BLR code
		// is still a potential danger and must be fixed.
		// Hopefully, system triggers are never recompiled.
		dsqlScratch->appendUChar(blr_label);
		dsqlScratch->appendUChar(0);

		stmtNode->genBlr(dsqlScratch);

		dsqlScratch->scopeLevel--;
		dsqlScratch->appendUChar(blr_end);
		dsqlScratch->appendUChar(blr_eoc);

		dsqlScratch->endDebug();

		// The statement type may have been set incorrectly when parsing
		// the trigger actions, so reset it to reflect the fact that this
		// is a data definition statement; also reset the ddl node.
		dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_DDL);
	}

	invalid = false;
}


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


string DropTriggerNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, silent);

	return "DropTriggerNode";
}

DdlNode* DropTriggerNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	dsqlScratch->flags |= (DsqlCompilerScratch::FLAG_BLOCK | DsqlCompilerScratch::FLAG_TRIGGER);
	return DdlNode::dsqlPass(dsqlScratch);
}

void DropTriggerNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	MetaName relationName = getTriggerRelationName(tdbb, transaction, name);

	if (relationName.isEmpty())
		SCL_check_database(tdbb, SCL_alter);
	else
	{
		dsc dscName;
		dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());
		SCL_check_relation(tdbb, &dscName, SCL_alter);
	}
}

void DropTriggerNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	bool found = false;
	MetaName relationName;
	AutoCacheRequest requestHandle(tdbb, drq_e_trigger3, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		X IN RDB$TRIGGERS
		WITH X.RDB$TRIGGER_NAME EQ name.c_str()
	{
		switch (X.RDB$SYSTEM_FLAG)
		{
			case fb_sysflag_check_constraint:
			case fb_sysflag_referential_constraint:
			case fb_sysflag_view_check:
				status_exception::raise(Arg::Gds(isc_dyn_cant_modify_auto_trig));
				break;

			case fb_sysflag_system:
				status_exception::raise(
					Arg::Gds(isc_dyn_cannot_mod_systrig) << MetaName(X.RDB$TRIGGER_NAME));
				break;

			default:
				break;
		}

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_TRIGGER,
			name, NULL);

		relationName = X.RDB$RELATION_NAME;
		ERASE X;
		found = true;
	}
	END_FOR

	if (!found && !silent)
		status_exception::raise(Arg::Gds(isc_dyn_trig_not_found) << Arg::Str(name));

	requestHandle.reset(tdbb, drq_e_trg_msgs3, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		TM IN RDB$TRIGGER_MESSAGES
		WITH TM.RDB$TRIGGER_NAME EQ name.c_str()
	{
		ERASE TM;
	}
	END_FOR

	requestHandle.reset(tdbb, drq_e_trg_prv2, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$USER EQ name.c_str() AND
			 PRIV.RDB$USER_TYPE = obj_trigger AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	// Clear the update flags on the fields if this is the last remaining
	// trigger that changes a view.

	bool viewFound = false;
	requestHandle.reset(tdbb, drq_e_trg_prv3, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		FIRST 1 V IN RDB$VIEW_RELATIONS
		CROSS F IN RDB$RELATION_FIELDS
		CROSS T IN RDB$TRIGGERS
		WITH V.RDB$VIEW_NAME EQ relationName.c_str() AND
			 F.RDB$RELATION_NAME EQ V.RDB$VIEW_NAME AND
			 F.RDB$RELATION_NAME EQ T.RDB$RELATION_NAME
	{
		viewFound = true;
	}
	END_FOR

	if (!viewFound)
	{
		requestHandle.reset(tdbb, drq_m_rel_flds2, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			F IN RDB$RELATION_FIELDS
			WITH F.RDB$RELATION_NAME EQ relationName.c_str()
		{
			MODIFY F USING
				F.RDB$UPDATE_FLAG = FALSE;
			END_MODIFY
		}
		END_FOR
	}

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_TRIGGER,
			name, NULL);
	}

	savePoint.release();	// everything is ok
}


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


string CreateCollationNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, forCharSet);
	NODE_PRINT(printer, fromName);
	NODE_PRINT(printer, fromExternal);
	NODE_PRINT(printer, specificAttributes);
	NODE_PRINT(printer, attributesOn);
	NODE_PRINT(printer, attributesOff);
	NODE_PRINT(printer, forCharSetId);
	NODE_PRINT(printer, fromCollationId);

	return "CreateCollationNode";
}

void CreateCollationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_create_access(tdbb, SCL_object_collation);
}

void CreateCollationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;
	const MetaString& ownerName = attachment->getEffectiveUserName();

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
		DDL_TRIGGER_CREATE_COLLATION, name, NULL);

	AutoCacheRequest request(tdbb, drq_s_colls, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$COLLATIONS
	{
		X.RDB$CHARACTER_SET_ID = forCharSetId;
		strcpy(X.RDB$COLLATION_NAME, name.c_str());
		X.RDB$SYSTEM_FLAG = 0;

		X.RDB$OWNER_NAME.NULL = FALSE;
		strcpy(X.RDB$OWNER_NAME, ownerName.c_str());

		X.RDB$SPECIFIC_ATTRIBUTES.NULL = TRUE;
		X.RDB$BASE_COLLATION_NAME.NULL = TRUE;

		CharSet* cs = INTL_charset_lookup(tdbb, forCharSetId);
		SubtypeInfo info;

		if (fromName.hasData())
		{
			if (MET_get_char_coll_subtype_info(tdbb,
					INTL_CS_COLL_TO_TTYPE(forCharSetId, fromCollationId), &info) &&
				forCharSetId != CS_METADATA &&
				info.specificAttributes.hasData())
			{
				UCharBuffer temp;
				ULONG size = info.specificAttributes.getCount() * cs->maxBytesPerChar();

				size = INTL_convert_bytes(tdbb, forCharSetId, temp.getBuffer(size), size,
					CS_METADATA, info.specificAttributes.begin(),
					info.specificAttributes.getCount(), status_exception::raise);
				temp.shrink(size);
				info.specificAttributes = temp;
			}

			strcpy(X.RDB$BASE_COLLATION_NAME, info.baseCollationName.c_str());
			X.RDB$BASE_COLLATION_NAME.NULL = FALSE;
		}
		else if (fromExternal.hasData())
		{
			strcpy(X.RDB$BASE_COLLATION_NAME, fromExternal.c_str());
			X.RDB$BASE_COLLATION_NAME.NULL = FALSE;
		}

		if (specificAttributes.hasData() && forCharSetId != attachment->att_charset)
		{
			UCharBuffer temp;
			ULONG size = specificAttributes.getCount() * cs->maxBytesPerChar();

			size = INTL_convert_bytes(tdbb, forCharSetId, temp.getBuffer(size), size,
				attachment->att_charset, specificAttributes.begin(),
				specificAttributes.getCount(), status_exception::raise);
			temp.shrink(size);
			specificAttributes = temp;
		}

		info.charsetName = forCharSet.c_str();
		info.collationName = name;
		if (X.RDB$BASE_COLLATION_NAME.NULL)
			info.baseCollationName = info.collationName;
		else
			info.baseCollationName = X.RDB$BASE_COLLATION_NAME;
		info.ignoreAttributes = false;

		if (!IntlManager::collationInstalled(info.baseCollationName.c_str(),
				info.charsetName.c_str()))
		{
			// msg: 223: "Collation @1 not installed for character set @2"
			status_exception::raise(
				Arg::PrivateDyn(223) << info.baseCollationName << info.charsetName);
		}

		IntlUtil::SpecificAttributesMap map;

		if (!IntlUtil::parseSpecificAttributes(
				cs, info.specificAttributes.getCount(), info.specificAttributes.begin(), &map) ||
			!IntlUtil::parseSpecificAttributes(
				cs, specificAttributes.getCount(), specificAttributes.begin(), &map))
		{
			// msg: 222: "Invalid collation attributes"
			status_exception::raise(Arg::PrivateDyn(222));
		}

		const string s = IntlUtil::generateSpecificAttributes(cs, map);
		string newSpecificAttributes;

		if (!IntlManager::setupCollationAttributes(
				info.baseCollationName.c_str(), info.charsetName.c_str(), s,
				newSpecificAttributes))
		{
			// msg: 222: "Invalid collation attributes"
			status_exception::raise(Arg::PrivateDyn(222));
		}

		memcpy(info.specificAttributes.getBuffer(newSpecificAttributes.length()),
			newSpecificAttributes.begin(), newSpecificAttributes.length());

		if (info.specificAttributes.hasData())
		{
			X.RDB$SPECIFIC_ATTRIBUTES.NULL = FALSE;
			attachment->storeMetaDataBlob(tdbb, transaction, &X.RDB$SPECIFIC_ATTRIBUTES,
				string(info.specificAttributes.begin(), info.specificAttributes.getCount()),
				forCharSetId);
		}

		info.attributes = (info.attributes | attributesOn) & (~attributesOff);
		X.RDB$COLLATION_ATTRIBUTES = info.attributes;

		// Do not allow invalid attributes here.
		if (!INTL_texttype_validate(tdbb, &info))
		{
			// msg: 222: "Invalid collation attributes"
			status_exception::raise(Arg::PrivateDyn(222));
		}

		// ASF: User collations are created with the last number available,
		// to minimize the possibility of conflicts with future system collations.
		// The greater available number is 126 to avoid signed/unsigned problems.

		X.RDB$COLLATION_ID.NULL = TRUE;
		X.RDB$COLLATION_ID = 126;

		AutoCacheRequest request2(tdbb, drq_l_max_coll_id, DYN_REQUESTS);

		FOR(REQUEST_HANDLE request2)
			Y IN RDB$COLLATIONS
			WITH Y.RDB$CHARACTER_SET_ID = forCharSetId AND
					Y.RDB$COLLATION_ID NOT MISSING
			SORTED BY DESCENDING Y.RDB$COLLATION_ID
		{
			if (Y.RDB$COLLATION_ID + 1 <= X.RDB$COLLATION_ID)
			{
				X.RDB$COLLATION_ID.NULL = FALSE;
				break;
			}
			else
				X.RDB$COLLATION_ID = Y.RDB$COLLATION_ID - 1;
		}
		END_FOR

		if (X.RDB$COLLATION_ID.NULL)
			status_exception::raise(Arg::Gds(isc_max_coll_per_charset));
	}
	END_STORE

	storePrivileges(tdbb, transaction, name, obj_collation, USAGE_PRIVILEGES);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
		DDL_TRIGGER_CREATE_COLLATION, name, NULL);

	savePoint.release();	// everything is ok

	// Update DSQL cache
	METD_drop_collation(transaction, name);
	MET_dsql_cache_release(tdbb, SYM_intlsym_collation, name);
}

DdlNode* CreateCollationNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	const dsql_intlsym* resolvedCharSet = METD_get_charset(
		dsqlScratch->getTransaction(), forCharSet.length(), forCharSet.c_str());

	if (!resolvedCharSet)
	{
		// specified character set not found
		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
				  Arg::Gds(isc_charset_not_found) << forCharSet);
	}

	forCharSetId = resolvedCharSet->intlsym_charset_id;

	if (fromName.hasData())
	{
		const dsql_intlsym* resolvedCollation = METD_get_collation(
			dsqlScratch->getTransaction(), fromName, forCharSetId);

		if (!resolvedCollation)
		{
			// Specified collation not found
			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
					  Arg::Gds(isc_collation_not_found) << fromName << forCharSet);
		}

		fromCollationId = resolvedCollation->intlsym_collate_id;
	}

	return DdlNode::dsqlPass(dsqlScratch);
}


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


string DropCollationNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);

	return "DropCollationNode";
}

void DropCollationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_collation(tdbb, name, SCL_drop);
}

void DropCollationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	bool found = false;
	AutoCacheRequest request(tdbb, drq_e_colls, DYN_REQUESTS);

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		COLL IN RDB$COLLATIONS
		CROSS CS IN RDB$CHARACTER_SETS
		WITH COLL.RDB$COLLATION_NAME EQ name.c_str() AND
			 CS.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_DROP_COLLATION, name, NULL);

		if (COLL.RDB$SYSTEM_FLAG)
			status_exception::raise(Arg::Gds(isc_dyn_cannot_del_syscoll));

		if (COLL.RDB$COLLATION_ID == 0 ||
			(!CS.RDB$DEFAULT_COLLATE_NAME.NULL &&
				MetaName(COLL.RDB$COLLATION_NAME) == MetaName(CS.RDB$DEFAULT_COLLATE_NAME)))
		{
			fb_utils::exact_name_limit(CS.RDB$CHARACTER_SET_NAME,
				sizeof(CS.RDB$CHARACTER_SET_NAME));

			status_exception::raise(
				Arg::Gds(isc_dyn_cannot_del_def_coll) << CS.RDB$CHARACTER_SET_NAME);
		}

		found = true;
		fb_utils::exact_name_limit(COLL.RDB$COLLATION_NAME, sizeof(COLL.RDB$COLLATION_NAME));

		AutoCacheRequest request2(tdbb, drq_l_rfld_coll, DYN_REQUESTS);

		FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			RF IN RDB$RELATION_FIELDS
			CROSS F IN RDB$FIELDS
			WITH RF.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND
				 F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
				 RF.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
		{
			fb_utils::exact_name_limit(RF.RDB$RELATION_NAME, sizeof(RF.RDB$RELATION_NAME));
			fb_utils::exact_name_limit(RF.RDB$FIELD_NAME, sizeof(RF.RDB$FIELD_NAME));

			status_exception::raise(
				Arg::Gds(isc_dyn_coll_used_table) << COLL.RDB$COLLATION_NAME <<
				RF.RDB$RELATION_NAME << RF.RDB$FIELD_NAME);
		}
		END_FOR

		request2.reset(tdbb, drq_l_prm_coll, DYN_REQUESTS);

		FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			PRM IN RDB$PROCEDURE_PARAMETERS CROSS F IN RDB$FIELDS
			WITH PRM.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND
				 F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
				 PRM.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
		{
			fb_utils::exact_name_limit(PRM.RDB$PARAMETER_NAME, sizeof(PRM.RDB$PARAMETER_NAME));

			status_exception::raise(
				Arg::Gds(isc_dyn_coll_used_procedure) <<
				COLL.RDB$COLLATION_NAME <<
				QualifiedName(PRM.RDB$PROCEDURE_NAME,
					(PRM.RDB$PACKAGE_NAME.NULL ? NULL : PRM.RDB$PACKAGE_NAME)).toString().c_str() <<
				PRM.RDB$PARAMETER_NAME);
		}
		END_FOR

		request2.reset(tdbb, drq_l_arg_coll, DYN_REQUESTS);

		FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			ARG IN RDB$FUNCTION_ARGUMENTS CROSS F IN RDB$FIELDS
			WITH ARG.RDB$FIELD_SOURCE EQ F.RDB$FIELD_NAME AND
				 F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
				 ARG.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
		{
			fb_utils::exact_name_limit(ARG.RDB$ARGUMENT_NAME, sizeof(ARG.RDB$ARGUMENT_NAME));

			status_exception::raise(
				Arg::Gds(isc_dyn_coll_used_function) <<
				COLL.RDB$COLLATION_NAME <<
				QualifiedName(ARG.RDB$FUNCTION_NAME,
					(ARG.RDB$PACKAGE_NAME.NULL ? NULL : ARG.RDB$PACKAGE_NAME)).toString().c_str() <<
				ARG.RDB$ARGUMENT_NAME);
		}
		END_FOR

		request2.reset(tdbb, drq_l_fld_coll, DYN_REQUESTS);

		FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			F IN RDB$FIELDS
			WITH F.RDB$CHARACTER_SET_ID EQ COLL.RDB$CHARACTER_SET_ID AND
				 F.RDB$COLLATION_ID EQ COLL.RDB$COLLATION_ID
		{
			fb_utils::exact_name_limit(F.RDB$FIELD_NAME, sizeof(F.RDB$FIELD_NAME));

			status_exception::raise(
				Arg::Gds(isc_dyn_coll_used_domain) << COLL.RDB$COLLATION_NAME << F.RDB$FIELD_NAME);
		}
		END_FOR

		ERASE COLL;

		if (!COLL.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, COLL.RDB$SECURITY_CLASS);
	}
	END_FOR

	request.reset(tdbb, drq_e_coll_prvs, DYN_REQUESTS);

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
			 PRIV.RDB$OBJECT_TYPE = obj_collation AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_COLLATION,
			name, NULL);
	}
	else
		status_exception::raise(Arg::Gds(isc_dyn_collation_not_found) << Arg::Str(name));

	savePoint.release();	// everything is ok

	// Update DSQL cache
	METD_drop_collation(transaction, name);
	MET_dsql_cache_release(tdbb, SYM_intlsym_collation, name);
}


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


string CreateDomainNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, nameType);
	NODE_PRINT(printer, notNull);
	NODE_PRINT(printer, check);

	return "CreateDomainNode";
}

void CreateDomainNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_create_access(tdbb, SCL_object_domain);
}

void CreateDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;
	dsql_fld* type = nameType->type;

	// The commented line should be restored when implicit domains get their own sys flag.
	//if (fb_utils::implicit_domain(nameType->name.c_str()))
	if (strncmp(nameType->name.c_str(), IMPLICIT_DOMAIN_PREFIX, IMPLICIT_DOMAIN_PREFIX_LEN) == 0)
	{
		status_exception::raise(
			Arg::Gds(isc_sqlerr) << Arg::Num(-637) <<
			Arg::Gds(isc_dsql_implicit_domain_name) << nameType->name);
	}

	const ValueListNode* elements = type->ranges;
	const USHORT dims = elements ? elements->items.getCount() / 2 : 0;

	if (nameType->defaultClause && dims != 0)
	{
		// Default value is not allowed for array type in domain %s
		status_exception::raise(Arg::PrivateDyn(226) << nameType->name);
	}

	type->resolve(dsqlScratch);

	dsqlScratch->domainValue.dsc_dtype = type->dtype;
	dsqlScratch->domainValue.dsc_length = type->length;
	dsqlScratch->domainValue.dsc_scale = type->scale;

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
		DDL_TRIGGER_CREATE_DOMAIN, nameType->name, NULL);

	storeGlobalField(tdbb, transaction, nameType->name, type);

	if (nameType->defaultClause || check || notNull)
	{
		AutoCacheRequest request(tdbb, drq_m_fld, DYN_REQUESTS);

		FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			FLD IN RDB$FIELDS
			WITH FLD.RDB$FIELD_NAME EQ nameType->name.c_str()
		{
			MODIFY FLD
				if (nameType->defaultClause)
				{
					FLD.RDB$DEFAULT_SOURCE.NULL = FALSE;
					attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE,
						nameType->defaultClause->source);

					dsqlScratch->getBlrData().clear();
					dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

					ValueExprNode* node = doDsqlPass(dsqlScratch, nameType->defaultClause->value);

					GEN_expr(dsqlScratch, node);

					dsqlScratch->appendUChar(blr_eoc);

					FLD.RDB$DEFAULT_VALUE.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$DEFAULT_VALUE,
						dsqlScratch->getBlrData());
				}

				if (check)
				{
					FLD.RDB$VALIDATION_SOURCE.NULL = FALSE;
					attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE,
						check->source);

					dsqlScratch->getBlrData().clear();
					dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

					// Increment the context level for this statement, so that the context number for
					// any RSE generated for a SELECT within the CHECK clause will be greater than 0.
					// In the environment of a domain check constraint, context number 0 is reserved
					// for the "blr_fid, 0, 0, 0," which is emitted for a nod_dom_value, corresponding
					// to an occurance of the VALUE keyword in the body of the check constraint.
					// -- chrisj 1999-08-20
					++dsqlScratch->contextNumber;

					BoolExprNode* node = doDsqlPass(dsqlScratch, check->value);

					GEN_expr(dsqlScratch, node);

					dsqlScratch->appendUChar(blr_eoc);

					FLD.RDB$VALIDATION_BLR.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$VALIDATION_BLR,
						dsqlScratch->getBlrData());
				}

				if (notNull)
				{
					FLD.RDB$NULL_FLAG.NULL = FALSE;
					FLD.RDB$NULL_FLAG = 1;
				}
			END_MODIFY
		}
		END_FOR
	}

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
		DDL_TRIGGER_CREATE_DOMAIN, nameType->name, NULL);

	savePoint.release();	// everything is ok
}


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


// Compare the original field type with the new field type to determine if the original type can be
// changed to the new type.
//
// The following conversions are not allowed:
//   Blob to anything
//   Array to anything
//   Date to anything
//   Char to any numeric
//   Varchar to any numeric
//   Anything to Blob
//   Anything to Array
//
// This function throws an exception if the conversion can not be made.
//
// ASF: We should stop using dyn_fld here as soon DYN stops to be a caller of this function.
void AlterDomainNode::checkUpdate(const dyn_fld& origFld, const dyn_fld& newFld)
{
	ULONG errorCode = FB_SUCCESS;

	const USHORT origLen = DTYPE_IS_TEXT(origFld.dyn_dsc.dsc_dtype) ?
		origFld.dyn_charlen : DSC_string_length(&origFld.dyn_dsc);

	// Check to make sure that the old and new types are compatible
	switch (origFld.dyn_dtype)
	{
	// CHARACTER types
	case blr_text:
	case blr_varying:
	case blr_cstring:
		switch (newFld.dyn_dtype)
		{
		case blr_blob:
		case blr_blob_id:
			// Cannot change datatype for column %s.
			// The operation cannot be performed on BLOB, or ARRAY columns.
			errorCode = isc_dyn_dtype_invalid;
			break;

		case blr_sql_date:
		case blr_sql_time:
		case blr_sql_time_tz:
		case blr_timestamp:
		case blr_timestamp_tz:
		case blr_int64:
		case blr_long:
		case blr_short:
		case blr_d_float:
		case blr_double:
		case blr_float:
		case blr_dec64:
		case blr_dec128:
		case blr_int128:
			// Cannot convert column %s from character to non-character data.
			errorCode = isc_dyn_dtype_conv_invalid;
			break;

		// If the original field is a character field and the new field is a character field,
		// is there enough space in the new field?
		case blr_text:
		case blr_varying:
		case blr_cstring:
			if (newFld.dyn_charlen < origLen)
			{
				// msg 208: New size specified for column %s must be at least %d characters.
				errorCode = isc_dyn_char_fld_too_small;
			}
			break;

		default:
			fb_assert(FALSE);
			errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC);			// MODIFY RDB$FIELDS FAILED
			break;
		}
		break;

	// BLOB and ARRAY types
	case blr_blob:
	case blr_blob_id:
		// Cannot change datatype for column %s.
		// The operation cannot be performed on BLOB, or ARRAY columns.
		errorCode = isc_dyn_dtype_invalid;
		break;

	// DATE types
	case blr_sql_date:
	case blr_sql_time:
	case blr_sql_time_tz:
	case blr_timestamp:
	case blr_timestamp_tz:
		switch (newFld.dyn_dtype)
		{
		case blr_sql_date:
			if (origFld.dyn_dtype == blr_sql_time ||
				origFld.dyn_dtype == blr_sql_time_tz ||
				origFld.dyn_dtype == blr_timestamp_tz)
			{
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
			}
			break;

		case blr_sql_time:
			if (origFld.dyn_dtype == blr_sql_date ||
				origFld.dyn_dtype == blr_sql_time_tz ||
				origFld.dyn_dtype == blr_timestamp_tz)
			{
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
			}
			break;

		case blr_sql_time_tz:
			if (origFld.dyn_dtype == blr_sql_date ||
				origFld.dyn_dtype == blr_sql_time ||
				origFld.dyn_dtype == blr_timestamp)
			{
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
			}
			break;

		case blr_timestamp:
			if (origFld.dyn_dtype == blr_sql_time ||
				origFld.dyn_dtype == blr_sql_time_tz ||
				origFld.dyn_dtype == blr_timestamp_tz)
			{
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
			}
			break;

		case blr_timestamp_tz:
			if (origFld.dyn_dtype == blr_sql_date ||
				origFld.dyn_dtype == blr_sql_time ||
				origFld.dyn_dtype == blr_sql_time_tz ||
				origFld.dyn_dtype == blr_timestamp)
			{
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
			}
			break;

		// If the original field is a datetime field and the new field is a character field,
		// is there enough space in the new field?
		case blr_text:
		case blr_text2:
		case blr_varying:
		case blr_varying2:
		case blr_cstring:
		case blr_cstring2:
			if (newFld.dyn_charlen < origLen)
			{
				// msg 208: New size specified for column %s must be at least %d characters.
				errorCode = isc_dyn_char_fld_too_small;
			}
			break;

		default:
			// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
			errorCode = isc_dyn_invalid_dtype_conversion;
			break;
		}
		break;

	// NUMERIC types
	case blr_int64:
	case blr_long:
	case blr_short:
	case blr_d_float:
	case blr_double:
	case blr_float:
	case blr_dec64:
	case blr_dec128:
	case blr_int128:
		switch (newFld.dyn_dtype)
		{
		case blr_blob:
		case blr_blob_id:
			// Cannot change datatype for column %s.
			// The operation cannot be performed on BLOB, or ARRAY columns.
			errorCode = isc_dyn_dtype_invalid;
			break;

		case blr_sql_date:
		case blr_sql_time:
		case blr_sql_time_tz:
		case blr_timestamp:
		case blr_timestamp_tz:
			// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
			errorCode = isc_dyn_invalid_dtype_conversion;
			break;

		// If the original field is a numeric field and the new field is a numeric field,
		// is there enough space in the new field (do not allow the base type to decrease)

		case blr_short:
			switch (origFld.dyn_dtype)
			{
			case blr_short:
				errorCode = checkUpdateNumericType(origFld, newFld);
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		case blr_long:
			switch (origFld.dyn_dtype)
			{
			case blr_long:
			case blr_short:
				errorCode = checkUpdateNumericType(origFld, newFld);
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		case blr_float:
			switch (origFld.dyn_dtype)
			{
			case blr_float:
			case blr_short:
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		case blr_int64:
			switch (origFld.dyn_dtype)
			{
			case blr_int64:
			case blr_long:
			case blr_short:
				errorCode = checkUpdateNumericType(origFld, newFld);
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		case blr_d_float:
		case blr_double:
			switch (origFld.dyn_dtype)
			{
			case blr_double:
			case blr_d_float:
			case blr_float:
			case blr_short:
			case blr_long:
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		case blr_dec64:
			switch (origFld.dyn_dtype)
			{
			case blr_short:
			case blr_long:
			case blr_float:
			case blr_d_float:
			case blr_double:
			case blr_dec64:
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		case blr_dec128:
			switch (origFld.dyn_dtype)
			{
			case blr_short:
			case blr_long:
			case blr_int64:
			case blr_float:
			case blr_d_float:
			case blr_double:
			case blr_dec64:
			case blr_dec128:
			case blr_int128:
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		case blr_int128:
			switch (origFld.dyn_dtype)
			{
			case blr_short:
			case blr_long:
			case blr_int64:
			case blr_int128:
				break;

			default:
				// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
				errorCode = isc_dyn_invalid_dtype_conversion;
				break;
			}
			break;

		// If the original field is a numeric field and the new field is a character field,
		// is there enough space in the new field?
		case blr_text:
		case blr_varying:
		case blr_cstring:
			if (newFld.dyn_charlen < origLen)
			{
				// msg 208: New size specified for column %s must be at least %d characters.
				errorCode = isc_dyn_char_fld_too_small;
			}
			break;

		default:
			fb_assert(FALSE);
			errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC);			// MODIFY RDB$FIELDS FAILED
			break;
		}
		break;

	case blr_bool:
		switch (newFld.dyn_dtype)
		{
		case blr_bool:
			break;

		/*** ASF: I'm not yet sure about this, and it is not working internally.
		// If the original field is a boolean field and the new field is a character field,
		// is there enough space in the new field?
		case blr_text:
		case blr_varying:
		case blr_cstring:
			if (newFld.dyn_charlen < origLen)
			{
				// msg 208: New size specified for column %s must be at least %d characters.
				errorCode = isc_dyn_char_fld_too_small;
			}
			break;
		***/

		default:
			// Cannot change datatype for column %s.  Conversion from base type %s to base type %s is not supported.
			errorCode = isc_dyn_invalid_dtype_conversion;
			break;
		}
		break;

	default:
		fb_assert(FALSE);
		errorCode = ENCODE_ISC_MSG(87, DYN_MSG_FAC);				// MODIFY RDB$FIELDS FAILED
		break;
	}

	if (errorCode == FB_SUCCESS)
		return;

	switch (errorCode)
	{
	case isc_dyn_dtype_invalid:
		// Cannot change datatype for column %s.The operation cannot be performed on DATE, BLOB, or ARRAY columns.
		status_exception::raise(Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str());
		break;

	case isc_dyn_dtype_conv_invalid:
		// Cannot convert column %s from character to non-character data.
		status_exception::raise(Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str());
		break;

	case isc_dyn_char_fld_too_small:
		// msg 208: New size specified for column %s must be at least %d characters.
		status_exception::raise(
			Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str() << Arg::Num(origLen));
		break;

	case isc_dyn_scale_too_big:
		{
			int code = errorCode;
			int diff = newFld.dyn_precision -
				(origFld.dyn_precision + origFld.dyn_dsc.dsc_scale);
			if (diff < 0)
			{
				// If new scale becomes negative externally, the message is useless for the user.
				// (The scale is always zero or negative for us but externally is non-negative.)
				// Let's ask the user to widen the precision, then. Example: numeric(4, 0) -> numeric(1, 1).
				code = isc_dyn_precision_too_small;
				diff = newFld.dyn_precision - newFld.dyn_dsc.dsc_scale - diff;
			}

			// scale_too_big: New scale specified for column @1 must be at most @2.
			// precision_too_small: New precision specified for column @1 must be at least @2.
			status_exception::raise(
				Arg::Gds(code) << origFld.dyn_fld_name.c_str() << Arg::Num(diff));
		}
		break;

	case isc_dyn_invalid_dtype_conversion:
		{
			TEXT orig_type[25], new_type[25];

			DSC_get_dtype_name(&origFld.dyn_dsc, orig_type, sizeof(orig_type));
			DSC_get_dtype_name(&newFld.dyn_dsc, new_type, sizeof(new_type));

			// Cannot change datatype for @1.  Conversion from base type @2 to @3 is not supported.
			status_exception::raise(
				Arg::Gds(errorCode) << origFld.dyn_fld_name.c_str() << orig_type << new_type);
		}
		break;

	default:
		// msg 95: "MODIFY RDB$RELATION_FIELDS failed"
		status_exception::raise(Arg::PrivateDyn(95));
	}
}

// Compare the original field type with the new field type to determine if the original type can be
// changed to the new type.
// The types should be integral, since it tests only numeric/decimal subtypes to ensure the scale is
// not being widened at the expense of the precision, because the old stored values should fit in
// the new definition.
//
// This function returns an error code if the conversion can not be made. If the conversion can be
// made, FB_SUCCESS is returned.
ULONG AlterDomainNode::checkUpdateNumericType(const dyn_fld& origFld, const dyn_fld& newFld)
{
 	// Since dsc_scale is negative, the sum of precision and scale produces
	// the width of the integral part.
	if (origFld.dyn_sub_type && newFld.dyn_sub_type &&
		origFld.dyn_precision + origFld.dyn_dsc.dsc_scale >
			newFld.dyn_precision + newFld.dyn_dsc.dsc_scale)
	{
		return isc_dyn_scale_too_big;
	}

	return FB_SUCCESS;
}

// Retrieves the type information for a domain so that it can be compared to a local field before
// modifying the datatype of a field.
void AlterDomainNode::getDomainType(thread_db* tdbb, jrd_tra* transaction, dyn_fld& dynFld)
{
	AutoRequest request;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FLD IN RDB$FIELDS
		WITH FLD.RDB$FIELD_NAME EQ dynFld.dyn_fld_source.c_str();
	{
		DSC_make_descriptor(&dynFld.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE,
			FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID,
			FLD.RDB$COLLATION_ID);

		dynFld.dyn_charbytelen = FLD.RDB$FIELD_LENGTH;
		dynFld.dyn_dtype = FLD.RDB$FIELD_TYPE;
		dynFld.dyn_precision = FLD.RDB$FIELD_PRECISION;
		dynFld.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE;
		dynFld.dyn_charlen = FLD.RDB$CHARACTER_LENGTH;
		dynFld.dyn_collation = FLD.RDB$COLLATION_ID;
		dynFld.dyn_null_flag = FLD.RDB$NULL_FLAG != 0;

		if (!FLD.RDB$DIMENSIONS.NULL && FLD.RDB$DIMENSIONS > 0)
			dynFld.dyn_dtype = blr_blob;
	}
	END_FOR
}

// Updates the field names in an index and forces the index to be rebuilt with the new field names.
void AlterDomainNode::modifyLocalFieldIndex(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName, const MetaName& newFieldName)
{
	AutoRequest request;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES CROSS IDXS IN RDB$INDEX_SEGMENTS WITH
			IDX.RDB$INDEX_NAME EQ IDXS.RDB$INDEX_NAME AND
			IDX.RDB$RELATION_NAME EQ relationName.c_str() AND
			IDXS.RDB$FIELD_NAME EQ fieldName.c_str()
	{
		// Change the name of the field in the index
		MODIFY IDXS USING
			memcpy(IDXS.RDB$FIELD_NAME, newFieldName.c_str(), sizeof(IDXS.RDB$FIELD_NAME));
		END_MODIFY

		// Set the index name to itself to tell the index to rebuild
		MODIFY IDX USING
			// This is to fool both gpre and gcc.
			char* p = IDX.RDB$INDEX_NAME;
			p[MAX_SQL_IDENTIFIER_LEN] = 0;
		END_MODIFY
	}
	END_FOR
}

string AlterDomainNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, dropConstraint);
	NODE_PRINT(printer, dropDefault);
	NODE_PRINT(printer, setConstraint);
	NODE_PRINT(printer, setDefault);
	NODE_PRINT(printer, renameTo);
	NODE_PRINT(printer, type);
	NODE_PRINT(printer, notNullFlag);

	return "AlterDomainNode";
}

void AlterDomainNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_domain(tdbb, name, SCL_alter);
}

void AlterDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_m_fld2, DYN_REQUESTS);
	bool found = false;

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FLD IN RDB$FIELDS
		WITH FLD.RDB$FIELD_NAME EQ name.c_str()
	{
		found = true;

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_ALTER_DOMAIN, name, renameTo);

		if (FLD.RDB$SYSTEM_FLAG == fb_sysflag_system)
		{
			status_exception::raise(Arg::Gds(isc_dyn_cant_modify_sysobj) <<
				"domain" << Arg::Str(name));
		}

		MODIFY FLD
			if (dropConstraint)
			{
				FLD.RDB$VALIDATION_BLR.NULL = TRUE;
				FLD.RDB$VALIDATION_SOURCE.NULL = TRUE;
			}

			if (dropDefault)
			{
				FLD.RDB$DEFAULT_VALUE.NULL = TRUE;
				FLD.RDB$DEFAULT_SOURCE.NULL = TRUE;
			}

			if (setConstraint)
			{
				if (!FLD.RDB$VALIDATION_BLR.NULL)
				{
					// msg 160: "Only one constraint allowed for a domain"
					status_exception::raise(Arg::PrivateDyn(160));
				}

				dsql_fld localField(dsqlScratch->getPool());

				// Get the attributes of the domain, and set any occurrences of
				// keyword VALUE to the correct type, length, scale, etc.
				if (!METD_get_domain(dsqlScratch->getTransaction(), &localField, name))
				{
					// Specified domain or source field does not exist
					status_exception::raise(
						Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
						Arg::Gds(isc_dsql_command_err) <<
						Arg::Gds(isc_dsql_domain_not_found) << name);
				}

				dsqlScratch->domainValue.dsc_dtype = localField.dtype;
				dsqlScratch->domainValue.dsc_length = localField.length;
				dsqlScratch->domainValue.dsc_scale = localField.scale;

				FLD.RDB$VALIDATION_SOURCE.NULL = FALSE;
				attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$VALIDATION_SOURCE,
					setConstraint->source);

				dsqlScratch->getBlrData().clear();
				dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

				// Increment the context level for this statement, so that the context number for
				// any RSE generated for a SELECT within the CHECK clause will be greater than 0.
				// In the environment of a domain check constraint, context number 0 is reserved
				// for the "blr_fid, 0, 0, 0," which is emitted for a nod_dom_value, corresponding
				// to an occurance of the VALUE keyword in the body of the check constraint.
				// -- chrisj 1999-08-20
				++dsqlScratch->contextNumber;

				BoolExprNode* node = doDsqlPass(dsqlScratch, setConstraint->value);

				GEN_expr(dsqlScratch, node);

				dsqlScratch->appendUChar(blr_eoc);

				FLD.RDB$VALIDATION_BLR.NULL = FALSE;
				attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$VALIDATION_BLR,
					dsqlScratch->getBlrData());
			}

			if (setDefault)
			{
				if (FLD.RDB$DIMENSIONS)
				{
					// msg 226: "Default value is not allowed for array type in domain %s"
					status_exception::raise(Arg::PrivateDyn(226) << name);
				}

				FLD.RDB$DEFAULT_SOURCE.NULL = FALSE;
				attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$DEFAULT_SOURCE,
					setDefault->source);

				dsqlScratch->getBlrData().clear();
				dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

				ValueExprNode* node = doDsqlPass(dsqlScratch, setDefault->value);

				GEN_expr(dsqlScratch, node);

				dsqlScratch->appendUChar(blr_eoc);

				FLD.RDB$DEFAULT_VALUE.NULL = FALSE;
				attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$DEFAULT_VALUE,
					dsqlScratch->getBlrData());
			}

			if (notNullFlag.specified)
			{
				FLD.RDB$NULL_FLAG.NULL = FALSE;
				FLD.RDB$NULL_FLAG = notNullFlag.value;

				if (!notNullFlag.value)
				{
					AutoRequest request2;

					FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
						RFL IN RDB$RELATION_FIELDS CROSS
						IND IN RDB$INDICES CROSS
						INDSEG IN RDB$INDEX_SEGMENTS CROSS
						RELCON IN RDB$RELATION_CONSTRAINTS
						WITH RFL.RDB$FIELD_SOURCE EQ name.c_str() AND
							 (RFL.RDB$NULL_FLAG MISSING OR RFL.RDB$NULL_FLAG EQ 0) AND
							 IND.RDB$RELATION_NAME EQ RFL.RDB$RELATION_NAME AND
							 INDSEG.RDB$INDEX_NAME EQ IND.RDB$INDEX_NAME AND
							 INDSEG.RDB$FIELD_NAME EQ RFL.RDB$FIELD_NAME AND
							 RELCON.RDB$INDEX_NAME EQ INDSEG.RDB$INDEX_NAME AND
							 RELCON.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY
					{
						status_exception::raise(
							Arg::Gds(isc_domain_primary_key_notnull) <<
							MetaName(RFL.RDB$RELATION_NAME));
					}
					END_FOR
				}
			}

			if (type)
			{
				type->resolve(dsqlScratch);

				dyn_fld origDom, newDom;

				DSC_make_descriptor(&origDom.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE,
					FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID,
					FLD.RDB$COLLATION_ID);

				origDom.dyn_fld_name = name;
				origDom.dyn_charbytelen = FLD.RDB$FIELD_LENGTH;
				origDom.dyn_dtype = FLD.RDB$FIELD_TYPE;
				origDom.dyn_precision = FLD.RDB$FIELD_PRECISION;
				origDom.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE;
				origDom.dyn_charlen = FLD.RDB$CHARACTER_LENGTH;
				origDom.dyn_collation = FLD.RDB$COLLATION_ID;
				origDom.dyn_null_flag = !FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG != 0;

				// If the original field type is an array, force its blr type to blr_blob
				if (FLD.RDB$DIMENSIONS != 0)
					origDom.dyn_dtype = blr_blob;

				USHORT typeLength = type->length;
				switch (type->dtype)
				{
					case dtype_varying:
						typeLength -= sizeof(USHORT);
						break;

					// Not valid for domains, but may be important for a future refactor.
					case dtype_cstring:
						--typeLength;
						break;

					default:
						break;
				}

				DSC_make_descriptor(&newDom.dyn_dsc, blr_dtypes[type->dtype], type->scale,
					typeLength, type->subType, type->charSetId.value, type->collationId);

				newDom.dyn_fld_name = name;
				newDom.dyn_charbytelen = typeLength;
				newDom.dyn_dtype = blr_dtypes[type->dtype];
				newDom.dyn_precision = type->precision;
				newDom.dyn_sub_type = type->subType;
				newDom.dyn_charlen = type->charLength;
				newDom.dyn_collation = type->collationId;
				newDom.dyn_null_flag = type->notNull;

				// Now that we have all of the information needed, let's check to see if the field
				// type can be modifed.

				checkUpdate(origDom, newDom);

				if (!newDom.dyn_dsc.isExact() || newDom.dyn_dsc.dsc_scale != 0)
				{
					AutoCacheRequest request2(tdbb, drq_l_ident_gens, DYN_REQUESTS);

					FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
						RFR IN RDB$RELATION_FIELDS
						WITH RFR.RDB$FIELD_SOURCE = FLD.RDB$FIELD_NAME AND
							 RFR.RDB$GENERATOR_NAME NOT MISSING
					{
						// Domain @1 must be of exact number type with zero scale because it's used
						// in an identity column.
						status_exception::raise(Arg::PrivateDyn(276) << name);
					}
					END_FOR
				}

				// If the datatype was changed, update any indexes that involved the domain

				AutoRequest request2;

				FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
					DOM IN RDB$RELATION_FIELDS
					WITH DOM.RDB$FIELD_SOURCE EQ name.c_str()
				{
					modifyLocalFieldIndex(tdbb, transaction, DOM.RDB$RELATION_NAME,
						DOM.RDB$FIELD_NAME, DOM.RDB$FIELD_NAME);
				}
				END_FOR

				// Update RDB$FIELDS
				updateRdbFields(type,
					FLD.RDB$FIELD_TYPE,
					FLD.RDB$FIELD_LENGTH,
					FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
					FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
					FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
					FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
					FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
					FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
					FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);
			}

			if (renameTo.hasData())
			{
				// The commented line should be restored when implicit domains get their own sys flag.
				//if (fb_utils::implicit_domain(renameTo.c_str()))
				if (strncmp(renameTo.c_str(), IMPLICIT_DOMAIN_PREFIX, IMPLICIT_DOMAIN_PREFIX_LEN) == 0)
				{
					status_exception::raise(
						Arg::Gds(isc_sqlerr) << Arg::Num(-637) <<
						Arg::Gds(isc_dsql_implicit_domain_name) << renameTo);
				}

				rename(tdbb, transaction, (FLD.RDB$DIMENSIONS.NULL ? 0 : FLD.RDB$DIMENSIONS));
				strcpy(FLD.RDB$FIELD_NAME, renameTo.c_str());
			}
		END_MODIFY
	}
	END_FOR

	if (!found)
	{
		// msg 89: "Global field not found"
		status_exception::raise(Arg::PrivateDyn(89));
	}

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_DOMAIN,
		(renameTo.hasData() ? renameTo : name),
		(renameTo.hasData() ? name : NULL));

	savePoint.release();	// everything is ok
}

void AlterDomainNode::rename(thread_db* tdbb, jrd_tra* transaction, SSHORT dimensions)
{
	// Checks to see if the given domain already exists.

	AutoRequest request;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FLD IN RDB$FIELDS WITH FLD.RDB$FIELD_NAME EQ renameTo.c_str()
	{
		// msg 204: Cannot rename domain %s to %s.  A domain with that name already exists.
		status_exception::raise(Arg::PrivateDyn(204) << name << renameTo);
	}
	END_FOR

	// CVC: Let's update the dimensions, too.
	if (dimensions != 0)
	{
		request.reset();

		FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			FDIM IN RDB$FIELD_DIMENSIONS
			WITH FDIM.RDB$FIELD_NAME EQ name.c_str()
		{
			MODIFY FDIM USING
				strcpy(FDIM.RDB$FIELD_NAME, renameTo.c_str());
			END_MODIFY
		}
		END_FOR
	}

	request.reset();

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RFLD IN RDB$RELATION_FIELDS
		WITH RFLD.RDB$FIELD_SOURCE EQ name.c_str()
	{
		MODIFY RFLD USING
			strcpy(RFLD.RDB$FIELD_SOURCE, renameTo.c_str());
		END_MODIFY

		modifyLocalFieldIndex(tdbb, transaction, RFLD.RDB$RELATION_NAME,
			RFLD.RDB$FIELD_NAME, RFLD.RDB$FIELD_NAME);
	}
	END_FOR
}


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


// Delete the records in RDB$FIELD_DIMENSIONS pertaining to a field.
bool DropDomainNode::deleteDimensionRecords(thread_db* tdbb, jrd_tra* transaction, const MetaName& name)
{
	AutoCacheRequest request(tdbb, drq_e_dims, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$FIELD_DIMENSIONS
		WITH X.RDB$FIELD_NAME EQ name.c_str()
	{
		found = true;
		ERASE X;
	}
	END_FOR

	return found;
}

string DropDomainNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);

	return "DropDomainNode";
}

void DropDomainNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_domain(tdbb, name, SCL_drop);
}

void DropDomainNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_e_gfields, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$FIELDS
		WITH X.RDB$FIELD_NAME EQ name.c_str()
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_DROP_DOMAIN, name, NULL);

		check(tdbb, transaction);
		deleteDimensionRecords(tdbb, transaction, name);

		ERASE X;

		if (!X.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS);

		found = true;
	}
	END_FOR

	request.reset(tdbb, drq_e_gfld_prvs, DYN_REQUESTS);

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
			 PRIV.RDB$OBJECT_TYPE = obj_field AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_DOMAIN,
			name, NULL);
	}
	else
	{
		// msg 89: "Domain not found"
		status_exception::raise(Arg::PrivateDyn(89));
	}

	savePoint.release();	// everything is ok
}

void DropDomainNode::check(thread_db* tdbb, jrd_tra* transaction)
{
	AutoCacheRequest request(tdbb, drq_l_fld_src, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		Y IN RDB$RELATION_FIELDS
		WITH Y.RDB$FIELD_SOURCE EQ name.c_str()
	{
		fb_utils::exact_name_limit(Y.RDB$FIELD_SOURCE, sizeof(Y.RDB$FIELD_SOURCE));
		fb_utils::exact_name_limit(Y.RDB$RELATION_NAME, sizeof(Y.RDB$RELATION_NAME));
		fb_utils::exact_name_limit(Y.RDB$FIELD_NAME, sizeof(Y.RDB$FIELD_NAME));

		// msg 43: "Domain %s is used in table %s (local name %s) and can not be dropped"
		status_exception::raise(
			Arg::PrivateDyn(43) << Y.RDB$FIELD_SOURCE << Y.RDB$RELATION_NAME << Y.RDB$FIELD_NAME);
	}
	END_FOR

	request.reset(tdbb, drq_l_prp_src, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$PROCEDURE_PARAMETERS
		WITH X.RDB$FIELD_SOURCE EQ name.c_str()
	{
		fb_utils::exact_name_limit(X.RDB$FIELD_SOURCE, sizeof(X.RDB$FIELD_SOURCE));
		fb_utils::exact_name_limit(X.RDB$PROCEDURE_NAME, sizeof(X.RDB$PROCEDURE_NAME));
		fb_utils::exact_name_limit(X.RDB$PARAMETER_NAME, sizeof(X.RDB$PARAMETER_NAME));

		// msg 239: "Domain %s is used in procedure %s (parameter name %s) and cannot be dropped"
		status_exception::raise(
			Arg::PrivateDyn(239) << X.RDB$FIELD_SOURCE <<
				QualifiedName(X.RDB$PROCEDURE_NAME,
					(X.RDB$PACKAGE_NAME.NULL ? NULL : X.RDB$PACKAGE_NAME)).toString().c_str() <<
				X.RDB$PARAMETER_NAME);
	}
	END_FOR

	request.reset(tdbb, drq_l_arg_src, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$FUNCTION_ARGUMENTS
		WITH X.RDB$FIELD_SOURCE EQ name.c_str()
	{
		fb_utils::exact_name_limit(X.RDB$FIELD_SOURCE, sizeof(X.RDB$FIELD_SOURCE));
		fb_utils::exact_name_limit(X.RDB$FUNCTION_NAME, sizeof(X.RDB$FUNCTION_NAME));
		fb_utils::exact_name_limit(X.RDB$ARGUMENT_NAME, sizeof(X.RDB$ARGUMENT_NAME));

		// msg 239: "Domain %s is used in function %s (parameter name %s) and cannot be dropped"
		status_exception::raise(
			Arg::Gds(isc_dyn_domain_used_function) << X.RDB$FIELD_SOURCE <<
				QualifiedName(X.RDB$FUNCTION_NAME,
					(X.RDB$PACKAGE_NAME.NULL ? NULL : X.RDB$PACKAGE_NAME)).toString().c_str() <<
				X.RDB$ARGUMENT_NAME);
	}
	END_FOR
}


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


string CreateAlterExceptionNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, message);
	NODE_PRINT(printer, create);
	NODE_PRINT(printer, alter);

	return "CreateAlterExceptionNode";
}

void CreateAlterExceptionNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	if (alter)
	{
		if (SCL_check_exception(tdbb, name, SCL_alter) || !create)
			return;
	}

	SCL_check_create_access(tdbb, SCL_object_exception);
}

void CreateAlterExceptionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	fb_assert(create || alter);

	if (message.length() > XCP_MESSAGE_LENGTH)
		status_exception::raise(Arg::Gds(isc_dyn_name_longer));

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	if (alter)
	{
		if (!executeAlter(tdbb, dsqlScratch, transaction))
		{
			if (create)	// create or alter
				executeCreate(tdbb, dsqlScratch, transaction);
			else
			{
				// msg 144: "Exception not found"
				status_exception::raise(Arg::PrivateDyn(144));
			}
		}
	}
	else
		executeCreate(tdbb, dsqlScratch, transaction);

	savePoint.release();	// everything is ok
}

void CreateAlterExceptionNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->getAttachment();
	const MetaString& ownerName = attachment->getEffectiveUserName();

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
		DDL_TRIGGER_CREATE_EXCEPTION, name, NULL);

	DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_exception);

	AutoCacheRequest request(tdbb, drq_s_xcp, DYN_REQUESTS);
	int faults = 0;

	while (true)
	{
		try
		{
			SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_xcp_id, "RDB$EXCEPTIONS");
			id %= (MAX_SSHORT + 1);

			if (!id)
				continue;

			STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$EXCEPTIONS
			{
				X.RDB$EXCEPTION_NUMBER = id;
				X.RDB$SYSTEM_FLAG = 0;
				strcpy(X.RDB$EXCEPTION_NAME, name.c_str());

				X.RDB$OWNER_NAME.NULL = FALSE;
				strcpy(X.RDB$OWNER_NAME, ownerName.c_str());

				strcpy(X.RDB$MESSAGE, message.c_str());
			}
			END_STORE

			break;
		}
		catch (const status_exception& ex)
		{
			if (ex.value()[1] != isc_unique_key_violation)
				throw;

			if (++faults > MAX_SSHORT)
				throw;

			fb_utils::init_status(tdbb->tdbb_status_vector);
		}
	}

	storePrivileges(tdbb, transaction, name, obj_exception, USAGE_PRIVILEGES);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_EXCEPTION,
		name, NULL);
}

bool CreateAlterExceptionNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	AutoCacheRequest request(tdbb, drq_m_xcp, DYN_REQUESTS);
	bool modified = false;

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$EXCEPTIONS
		WITH X.RDB$EXCEPTION_NAME EQ name.c_str()
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_ALTER_EXCEPTION, name, NULL);

		MODIFY X
			strcpy(X.RDB$MESSAGE, message.c_str());

			modified = true;
		END_MODIFY
	}
	END_FOR

	if (modified)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_EXCEPTION,
			name, NULL);
	}

	return modified;
}


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


string DropExceptionNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, silent);

	return "DropExceptionNode";
}

void DropExceptionNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_exception(tdbb, name, SCL_drop);
}

void DropExceptionNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_e_xcp, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$EXCEPTIONS
	    WITH X.RDB$EXCEPTION_NAME EQ name.c_str()
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_DROP_EXCEPTION, name, NULL);
		ERASE X;

		if (!X.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, X.RDB$SECURITY_CLASS);

		found = true;
	}
	END_FOR

	request.reset(tdbb, drq_e_xcp_prvs, DYN_REQUESTS);

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
			 PRIV.RDB$OBJECT_TYPE = obj_exception AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_EXCEPTION,
			name, NULL);
	}
	else if (!silent)
	{
		// msg 144: "Exception not found"
		status_exception::raise(Arg::PrivateDyn(144));
	}

	savePoint.release();	// everything is ok
}


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


string CreateAlterSequenceNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, create);
	NODE_PRINT(printer, alter);
	NODE_PRINT(printer, legacy);
	NODE_PRINT(printer, restartSpecified);
	NODE_PRINT(printer, name);
	NODE_PRINT(printer, value);
	NODE_PRINT(printer, step);

	return "CreateAlterSequenceNode";
}

void CreateAlterSequenceNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	if (alter)
	{
		if (SCL_check_generator(tdbb, name, SCL_alter) || !create)
			return;
	}

	SCL_check_create_access(tdbb, SCL_object_generator);
}

void CreateAlterSequenceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	fb_assert(create || alter);

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	if (alter)
	{
		if (!executeAlter(tdbb, dsqlScratch, transaction))
		{
			if (create)	// create or alter
				executeCreate(tdbb, dsqlScratch, transaction);
			else
			{
				// msg 214: "Sequence not found"
				status_exception::raise(Arg::PrivateDyn(214) << name);
			}
		}
	}
	else
		executeCreate(tdbb, dsqlScratch, transaction);

	savePoint.release();	// everything is ok

}

void CreateAlterSequenceNode::putErrorPrefix(Firebird::Arg::StatusVector& statusVector)
{
	// Possibilities I see in parse.y
	//CREATE SEQ -> !legacy, create, !alter
	//REPL SEQ (create or alter) -> !legacy, create, alter
	//ALTER SEQ -> !legacy, !create, alter
	//SET GENERATOR -> legacy, !create, alter
	ISC_STATUS rc = 0;
	if (legacy)
	{
		if (alter)
			rc = isc_dsql_set_generator_failed;
		else
			rc = isc_dsql_create_sequence_failed; // no way to distinguish
	}
	else
	{
		if (alter)
			rc = isc_dsql_alter_sequence_failed;
		else
			rc = isc_dsql_create_sequence_failed;
	}
	statusVector << Firebird::Arg::Gds(rc) << name;
}

void CreateAlterSequenceNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_SEQUENCE,
		name, NULL);

	const SINT64 val = value.orElse(1);
	SLONG initialStep = 1;
	if (step.specified)
	{
		initialStep = step.value;
		if (initialStep == 0)
			status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_increment) << Arg::Str(name));
	}
	store(tdbb, transaction, name, fb_sysflag_user, val, initialStep);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_SEQUENCE,
		name, NULL);
}

bool CreateAlterSequenceNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	bool forbidden = false;

	if (legacy)
	{
		// The only need for this code is that for the sake of backward compatibility
		// SET GENERATOR is still described as isc_info_sql_stmt_set_generator and ISQL
		// treats it as DML thus executing it in a separate transaction. So we need to ensure
		// that the generator created in another transaction can be found here. This is done
		// using MET_lookup_generator() which works in the system transaction.

		SLONG oldStep = 0;
		const SLONG id = MET_lookup_generator(tdbb, name, &forbidden, &oldStep);
		if (id < 0)
			return false;

		if (forbidden && !tdbb->getAttachment()->isRWGbak())
			status_exception::raise(Arg::Gds(isc_dyn_cant_modify_sysobj) << "generator" << Arg::Str(name));

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_SEQUENCE,
			name, NULL);

		fb_assert(restartSpecified && value.specified);
		const SINT64 val = value.specified ? value.value : 0;
		if (step.specified)
		{
			const SLONG newStep = step.value;
			if (newStep == 0)
				status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_increment) << Arg::Str(name));

			// Perhaps it's better to move this to DFW?
			if (newStep != oldStep)
				MET_update_generator_increment(tdbb, id, newStep);
		}

		transaction->getGenIdCache()->put(id, val);
		dsc desc;
		desc.makeText((USHORT) name.length(), ttype_metadata, (UCHAR*) name.c_str());
		DFW_post_work(transaction, dfw_set_generator, &desc, id);

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_SEQUENCE,
			name, NULL);

		return true;
	}

	AutoCacheRequest request(tdbb, drq_l_gens, DYN_REQUESTS);

	bool found = false;

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$GENERATORS
		WITH X.RDB$GENERATOR_NAME EQ name.c_str()
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_SEQUENCE,
			name, NULL);

		if (X.RDB$SYSTEM_FLAG == fb_sysflag_system)
		{
			forbidden = true;
			break;
		}

		const SLONG id = X.RDB$GENERATOR_ID;

		if (step.specified)
		{
			const SLONG newStep = step.value;
			if (newStep == 0)
				status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_increment) << Arg::Str(name));

			if (newStep != X.RDB$GENERATOR_INCREMENT)
			{
				MODIFY X
					X.RDB$GENERATOR_INCREMENT = newStep;
				END_MODIFY
			}
		}

		if (restartSpecified)
		{
			const SINT64 oldValue = !X.RDB$INITIAL_VALUE.NULL ? X.RDB$INITIAL_VALUE : 0;
			const SINT64 newValue = value.specified ? value.value : oldValue;

			transaction->getGenIdCache()->put(id,
				newValue - (!X.RDB$GENERATOR_INCREMENT.NULL ? X.RDB$GENERATOR_INCREMENT : 1));
		}

		dsc desc;
		desc.makeText((USHORT) name.length(), ttype_metadata, (UCHAR*) name.c_str());
		DFW_post_work(transaction, dfw_set_generator, &desc, id);

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_SEQUENCE,
			name, NULL);

		found = true;
	}
	END_FOR

	if (forbidden)
		status_exception::raise(Arg::Gds(isc_dyn_cant_modify_sysobj) << "generator" << Arg::Str(name));

	return found;
}

SSHORT CreateAlterSequenceNode::store(thread_db* tdbb, jrd_tra* transaction, const MetaName& name,
	fb_sysflag sysFlag, SINT64 val, SLONG step)
{
	Attachment* const attachment = transaction->tra_attachment;
	const MetaString& ownerName = attachment->getEffectiveUserName();

	DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_generator);

	AutoCacheRequest request(tdbb, drq_s_gens, DYN_REQUESTS);
	int faults = 0;
	SSHORT storedId = -1;

	while (true)
	{
		try
		{
			SINT64 id = DYN_UTIL_gen_unique_id(tdbb, drq_g_nxt_gen_id, MASTER_GENERATOR);

			id %= MAX_SSHORT + 1;
			if (id == 0)
				continue;

			STORE (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$GENERATORS
			{
				X.RDB$GENERATOR_ID = id;
				X.RDB$SYSTEM_FLAG = (SSHORT) sysFlag;
				strcpy(X.RDB$GENERATOR_NAME, name.c_str());

				X.RDB$OWNER_NAME.NULL = FALSE;
				strcpy(X.RDB$OWNER_NAME, ownerName.c_str());

				X.RDB$INITIAL_VALUE.NULL = FALSE;
				X.RDB$INITIAL_VALUE = val;

				X.RDB$GENERATOR_INCREMENT = step;
			}
			END_STORE

			storedId = id;
			break;
		}
		catch (const status_exception& ex)
		{
			if (ex.value()[1] != isc_unique_key_violation)
				throw;

			if (++faults > MAX_SSHORT)
				throw;

			fb_utils::init_status(tdbb->tdbb_status_vector);
		}
	}

	storePrivileges(tdbb, transaction, name, obj_generator, USAGE_PRIVILEGES);

	// The STORE above has caused the DFW item to be posted, so we just adjust the cached
	// generator value.
	transaction->getGenIdCache()->put(storedId, val - step);

	return storedId;
}


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


string DropSequenceNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, silent);

	return "DropSequenceNode";
}

void DropSequenceNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_generator(tdbb, name, SCL_drop);
}

void DropSequenceNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_e_gens, DYN_REQUESTS);
	bool found = false;

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		GEN IN RDB$GENERATORS
		WITH GEN.RDB$GENERATOR_NAME EQ name.c_str()
	{
		if (GEN.RDB$SYSTEM_FLAG != 0)
		{
			// msg 272: "Cannot delete system generator @1"
			status_exception::raise(Arg::PrivateDyn(272) << name);
		}

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_DROP_SEQUENCE, name, NULL);

		ERASE GEN;

		if (!GEN.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, GEN.RDB$SECURITY_CLASS);

		found = true;
	}
	END_FOR

	request.reset(tdbb, drq_e_gen_prvs, DYN_REQUESTS);

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
			 PRIV.RDB$OBJECT_TYPE = obj_generator AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_SEQUENCE,
			name, NULL);
	}
	else if (!silent)
		status_exception::raise(Arg::Gds(isc_gennotdef) << Arg::Str(name));

	savePoint.release();	// everything is ok
}


// Delete a record from RDB$GENERATORS, without verifying RDB$SYSTEM_FLAG.
void DropSequenceNode::deleteIdentity(thread_db* tdbb, jrd_tra* transaction, const MetaName& name)
{
	AutoCacheRequest request(tdbb, drq_e_ident_gens, DYN_REQUESTS);

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		GEN IN RDB$GENERATORS
		WITH GEN.RDB$GENERATOR_NAME EQ name.c_str()
	{
		ERASE GEN;

		if (!GEN.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, GEN.RDB$SECURITY_CLASS);
	}
	END_FOR

	AutoRequest request2;

	FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
			 PRIV.RDB$OBJECT_TYPE = obj_generator AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR
}


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


RelationNode::RelationNode(MemoryPool& p, RelationSourceNode* aDsqlNode)
	: DdlNode(p),
	  dsqlNode(aDsqlNode),
	  name(p, dsqlNode->dsqlName),
	  clauses(p)
{
}

void RelationNode::FieldDefinition::modify(thread_db* tdbb, jrd_tra* transaction)
{
	AutoRequest request;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RFR IN RDB$RELATION_FIELDS
		WITH RFR.RDB$RELATION_NAME EQ relationName.c_str() AND
			 RFR.RDB$FIELD_NAME EQ name.c_str()
	{
		// ASF: This is prepared only to modify view fields!

		MODIFY RFR
			strcpy(RFR.RDB$FIELD_SOURCE, fieldSource.c_str());

			RFR.RDB$COLLATION_ID.NULL = TRUE;
			RFR.RDB$GENERATOR_NAME.NULL = TRUE;
			RFR.RDB$IDENTITY_TYPE.NULL = TRUE;
			RFR.RDB$NULL_FLAG.NULL = TRUE;
			RFR.RDB$DEFAULT_SOURCE.NULL = TRUE;
			RFR.RDB$DEFAULT_VALUE.NULL = TRUE;
			RFR.RDB$FIELD_POSITION.NULL = TRUE;

			RFR.RDB$VIEW_CONTEXT.NULL = TRUE;
			RFR.RDB$BASE_FIELD.NULL = TRUE;
			///RFR.RDB$UPDATE_FLAG.NULL = TRUE;

			if (collationId.specified)
			{
				RFR.RDB$COLLATION_ID.NULL = FALSE;
				RFR.RDB$COLLATION_ID = collationId.value;
			}

			SLONG fieldPos = -1;

			if (position.specified)
				fieldPos = position.value;
			else
			{
				DYN_UTIL_generate_field_position(tdbb, relationName, &fieldPos);
				if (fieldPos >= 0)
					++fieldPos;
			}

			if (fieldPos >= 0)
			{
				RFR.RDB$FIELD_POSITION.NULL = FALSE;
				RFR.RDB$FIELD_POSITION = SSHORT(fieldPos);
			}

			if (baseField.hasData())
			{
				RFR.RDB$BASE_FIELD.NULL = FALSE;
				strcpy(RFR.RDB$BASE_FIELD, baseField.c_str());
			}

			if (viewContext.specified)
			{
				fb_assert(baseField.hasData());

				RFR.RDB$VIEW_CONTEXT.NULL = FALSE;
				RFR.RDB$VIEW_CONTEXT = viewContext.value;

				DYN_UTIL_find_field_source(tdbb, transaction, relationName, viewContext.value,
					baseField.c_str(), RFR.RDB$FIELD_SOURCE);
			}
		END_MODIFY
	}
	END_FOR
}

void RelationNode::FieldDefinition::store(thread_db* tdbb, jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;

	AutoCacheRequest request(tdbb, drq_s_lfields, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RFR IN RDB$RELATION_FIELDS
	{
		strcpy(RFR.RDB$FIELD_NAME, name.c_str());
		strcpy(RFR.RDB$RELATION_NAME, relationName.c_str());
		strcpy(RFR.RDB$FIELD_SOURCE, fieldSource.c_str());
		RFR.RDB$SYSTEM_FLAG = 0;

		RFR.RDB$COLLATION_ID.NULL = TRUE;
		RFR.RDB$GENERATOR_NAME.NULL = TRUE;
		RFR.RDB$IDENTITY_TYPE.NULL = TRUE;
		RFR.RDB$NULL_FLAG.NULL = TRUE;
		RFR.RDB$DEFAULT_SOURCE.NULL = TRUE;
		RFR.RDB$DEFAULT_VALUE.NULL = TRUE;
		RFR.RDB$FIELD_POSITION.NULL = TRUE;

		RFR.RDB$VIEW_CONTEXT.NULL = TRUE;
		RFR.RDB$BASE_FIELD.NULL = TRUE;
		///RFR.RDB$UPDATE_FLAG.NULL = TRUE;

		if (collationId.specified)
		{
			RFR.RDB$COLLATION_ID.NULL = FALSE;
			RFR.RDB$COLLATION_ID = collationId.value;
		}

		if (identitySequence.hasData())
		{
			RFR.RDB$GENERATOR_NAME.NULL = FALSE;
			strcpy(RFR.RDB$GENERATOR_NAME, identitySequence.c_str());

			RFR.RDB$IDENTITY_TYPE.NULL = FALSE;
			RFR.RDB$IDENTITY_TYPE = identityType.value;
		}

		if (notNullFlag.specified)
		{
			RFR.RDB$NULL_FLAG.NULL = FALSE;
			RFR.RDB$NULL_FLAG = notNullFlag.value;
		}

		if (defaultSource.hasData())
		{
			RFR.RDB$DEFAULT_SOURCE.NULL = FALSE;
			attachment->storeMetaDataBlob(tdbb, transaction, &RFR.RDB$DEFAULT_SOURCE,
				defaultSource);
		}

		if (defaultValue.length > 0)
		{
			RFR.RDB$DEFAULT_VALUE.NULL = FALSE;
			attachment->storeBinaryBlob(tdbb, transaction, &RFR.RDB$DEFAULT_VALUE, defaultValue);
		}

		SLONG fieldPos = -1;

		if (position.specified)
			fieldPos = position.value;
		else
		{
			DYN_UTIL_generate_field_position(tdbb, relationName, &fieldPos);
			if (fieldPos >= 0)
				++fieldPos;
		}

		if (fieldPos >= 0)
		{
			RFR.RDB$FIELD_POSITION.NULL = FALSE;
			RFR.RDB$FIELD_POSITION = SSHORT(fieldPos);
		}

		if (baseField.hasData())
		{
			RFR.RDB$BASE_FIELD.NULL = FALSE;
			strcpy(RFR.RDB$BASE_FIELD, baseField.c_str());
		}

		if (viewContext.specified)
		{
			fb_assert(baseField.hasData());

			RFR.RDB$VIEW_CONTEXT.NULL = FALSE;
			RFR.RDB$VIEW_CONTEXT = viewContext.value;

			DYN_UTIL_find_field_source(tdbb, transaction, relationName, viewContext.value,
				baseField.c_str(), RFR.RDB$FIELD_SOURCE);
		}
	}
	END_STORE
}

// Delete local field.
//
//  The rules for dropping a regular column:
//
//   1. the column is not referenced in any views.
//   2. the column is not part of any user defined indexes.
//   3. the column is not used in any SQL statements inside of store
//        procedures or triggers
//   4. the column is not part of any check-constraints
//
// The rules for dropping a column that was created as primary key:
//
//   1. the column is not defined as any foreign keys
//   2. the column is not defined as part of compound primary keys
//
// The rules for dropping a column that was created as foreign key:
//
//   1. the column is not defined as a compound foreign key. A
//        compound foreign key is a foreign key consisted of more
//        than one columns.
//
// The RI enforcement for dropping primary key column is done by system
// triggers and the RI enforcement for dropping foreign key column is
// done by code and system triggers. See the functional description of
// deleteKeyConstraint function for detail.
void RelationNode::deleteLocalField(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relationName, const MetaName& fieldName)
{
	AutoCacheRequest request(tdbb, drq_l_dep_flds, DYN_REQUESTS);
	bool found = false;

	// Make sure that column is not referenced in any views.

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$RELATION_FIELDS CROSS
		Y IN RDB$RELATION_FIELDS CROSS
		Z IN RDB$VIEW_RELATIONS
		WITH X.RDB$RELATION_NAME EQ relationName.c_str() AND
			 X.RDB$FIELD_NAME EQ fieldName.c_str() AND
			 X.RDB$FIELD_NAME EQ Y.RDB$BASE_FIELD AND
			 X.RDB$FIELD_SOURCE EQ Y.RDB$FIELD_SOURCE AND
			 Y.RDB$RELATION_NAME EQ Z.RDB$VIEW_NAME AND
			 X.RDB$RELATION_NAME EQ Z.RDB$RELATION_NAME AND
			 Y.RDB$VIEW_CONTEXT EQ Z.RDB$VIEW_CONTEXT
	{
		// msg 52: "field %s from relation %s is referenced in view %s"
		status_exception::raise(
			Arg::PrivateDyn(52) << fieldName << relationName << Y.RDB$RELATION_NAME);
	}
	END_FOR

	// If the column to be dropped is being used as a foreign key
	// and the column was not part of any compound foreign key,
	// then we can drop the column. But we have to drop the foreign key
	// constraint first.

	request.reset(tdbb, drq_g_rel_constr_nm, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES CROSS
		IDX_SEG IN RDB$INDEX_SEGMENTS CROSS
		REL_CONST IN RDB$RELATION_CONSTRAINTS
		WITH IDX.RDB$RELATION_NAME EQ relationName.c_str() AND
			 REL_CONST.RDB$RELATION_NAME EQ relationName.c_str() AND
			 IDX_SEG.RDB$FIELD_NAME EQ fieldName.c_str() AND
			 IDX.RDB$INDEX_NAME EQ IDX_SEG.RDB$INDEX_NAME AND
			 IDX.RDB$INDEX_NAME EQ REL_CONST.RDB$INDEX_NAME AND
			 REL_CONST.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY
	{
		if (IDX.RDB$SEGMENT_COUNT == 1)
		{
			deleteKeyConstraint(tdbb, transaction, relationName,
				REL_CONST.RDB$CONSTRAINT_NAME, IDX.RDB$INDEX_NAME);
		}
		else
		{
			// msg 187: "field %s from relation %s is referenced in index %s"
			status_exception::raise(
				Arg::PrivateDyn(187) << fieldName << relationName << IDX.RDB$INDEX_NAME);
		}
	}
	END_FOR

	// Make sure that column is not referenced in any user-defined indexes.

	// NOTE: You still could see the system generated indices even though
	// they were already been deleted when dropping column that was used
	// as foreign key before "commit".

	request.reset(tdbb, drq_e_l_idx, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES CROSS
		IDX_SEG IN RDB$INDEX_SEGMENTS
		WITH IDX.RDB$INDEX_NAME EQ IDX_SEG.RDB$INDEX_NAME AND
			 IDX.RDB$RELATION_NAME EQ relationName.c_str() AND
			 IDX_SEG.RDB$FIELD_NAME EQ fieldName.c_str() AND
			 NOT ANY REL_CONST IN RDB$RELATION_CONSTRAINTS
				WITH REL_CONST.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME AND
					 REL_CONST.RDB$INDEX_NAME EQ IDX.RDB$INDEX_NAME
	{
		// msg 187: "field %s from relation %s is referenced in index %s"
		status_exception::raise(
			Arg::PrivateDyn(187) <<
				fieldName << relationName <<
				fb_utils::exact_name_limit(IDX.RDB$INDEX_NAME, sizeof(IDX.RDB$INDEX_NAME)));
	}
	END_FOR

	// Delete the automatically created generator for Identity columns.

	request.reset(tdbb, drq_e_lfield, DYN_REQUESTS);

	found = false;
	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RFR IN RDB$RELATION_FIELDS
		WITH RFR.RDB$FIELD_NAME EQ fieldName.c_str() AND
			 RFR.RDB$RELATION_NAME EQ relationName.c_str()
	{
		if (!RFR.RDB$GENERATOR_NAME.NULL)
			DropSequenceNode::deleteIdentity(tdbb, transaction, RFR.RDB$GENERATOR_NAME);

		ERASE RFR;

		if (!RFR.RDB$SECURITY_CLASS.NULL &&
			!strncmp(RFR.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN))
		{
			deleteSecurityClass(tdbb, transaction, RFR.RDB$SECURITY_CLASS);
		}

		found = true;
		DropRelationNode::deleteGlobalField(tdbb, transaction, RFR.RDB$FIELD_SOURCE);
	}
	END_FOR

	request.reset(tdbb, drq_e_fld_prvs, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$RELATION_NAME EQ relationName.c_str() AND
			 PRIV.RDB$FIELD_NAME EQ fieldName.c_str() AND
		     PRIV.RDB$OBJECT_TYPE = obj_relation AND
		     PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	if (!found)
	{
		// msg 176: "column %s does not exist in table/view %s"
		status_exception::raise(Arg::PrivateDyn(176) << fieldName << relationName);
	}
}

void RelationNode::defineField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, AddColumnClause* clause, SSHORT position,
	const ObjectsArray<MetaName>* pkCols)
{
	dsql_fld* field = clause->field;

	// Add the field to the relation being defined for parsing purposes.

	bool permanent = false;
	dsql_rel* relation = dsqlScratch->relation;
	if (relation != NULL)
	{
		if (!(relation->rel_flags & REL_new_relation))
		{
			dsql_fld* permField = FB_NEW_POOL(dsqlScratch->getAttachment()->dbb_pool) dsql_fld(
				dsqlScratch->getAttachment()->dbb_pool);

			*permField = *field;
			field = permField;
			permanent = true;
		}

		field->fld_next = relation->rel_fields;
		relation->rel_fields = field;
	}

	try
	{
		FieldDefinition fieldDefinition(*tdbb->getDefaultPool());

		if (field->typeOfName.hasData())
		{
			// Get the domain information.
			if (!METD_get_domain(transaction, field, field->typeOfName))
			{
				// Specified domain or source field does not exist
				status_exception::raise(
					Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
					Arg::Gds(isc_dsql_command_err) <<
					Arg::Gds(isc_dsql_domain_not_found) << field->typeOfName);
			}

			fieldDefinition.fieldSource = field->typeOfName;
		}

		// Check for constraints.
		ObjectsArray<CreateDropConstraint> constraints;
		bool notNullFlag = false;

		if (clause->identityOptions)
			notNullFlag = true;	// identity columns are implicitly not null

		for (ObjectsArray<AddConstraintClause>::iterator ptr = clause->constraints.begin();
			 ptr != clause->constraints.end(); ++ptr)
		{
			makeConstraint(tdbb, dsqlScratch, transaction, &*ptr, constraints, &notNullFlag);
		}

		if (!notNullFlag && pkCols)
		{
			// Let's see if the field appears in a "primary_key (a, b, c)" relation constraint.
			for (FB_SIZE_T i = 0; !notNullFlag && i < pkCols->getCount(); ++i)
			{
				if (field->fld_name == (*pkCols)[i].c_str())
					notNullFlag = true;
			}
		}

		fieldDefinition.relationName = name;
		fieldDefinition.name = field->fld_name;

		if (notNullFlag)
			fieldDefinition.notNullFlag = true;	// else, leave it not specified

		if (position >= 0)
			fieldDefinition.position = position;

		if (field->typeOfName.isEmpty())
		{
			string computedSource;
			BlrDebugWriter::BlrData computedValue;

			if (clause->computed)
			{
				field->flags |= FLD_computed;

				defineComputed(dsqlScratch, dsqlNode, field, clause->computed,
					computedSource, computedValue);
			}

			field->collate = clause->collate;
			field->resolve(dsqlScratch);

			// Generate a domain.
			storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, field,
				computedSource, computedValue);
		}

		if ((relation->rel_flags & REL_external) &&
			(field->dtype == dtype_blob || field->dtype == dtype_array || field->dimensions))
		{
			const char* typeName = (field->dtype == dtype_blob ? "BLOB" : "ARRAY");

			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_dsql_type_not_supp_ext_tab) << typeName << name << field->fld_name);
		}

		if (clause->collate.hasData())
			DDL_resolve_intl_type(dsqlScratch, field, clause->collate);

		if (clause->identityOptions)
		{
			if (clause->identityOptions->increment.orElse(1) == 0)
			{
				status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_inc_ident) <<
					Arg::Str(field->fld_name) <<
					Arg::Str(name));
			}

			dsc desc;
			MET_get_domain(tdbb, *tdbb->getDefaultPool(), fieldDefinition.fieldSource, &desc, NULL);

			if (!desc.isExact() || desc.dsc_scale != 0)
			{
				// Identity column @1 of table @2 must be exact numeric with zero scale.
				status_exception::raise(Arg::PrivateDyn(273) << field->fld_name << name);
			}

			DYN_UTIL_generate_generator_name(tdbb, fieldDefinition.identitySequence);
			fieldDefinition.identityType = clause->identityOptions->type;

			CreateAlterSequenceNode::store(tdbb, transaction, fieldDefinition.identitySequence,
				fb_sysflag_identity_generator,
				clause->identityOptions->startValue.orElse(1),
				clause->identityOptions->increment.orElse(1));
		}

		BlrDebugWriter::BlrData defaultValue;

		if (clause->defaultValue)
		{
			if (defineDefault(tdbb, dsqlScratch, field, clause->defaultValue,
					fieldDefinition.defaultSource, defaultValue) &&
				notNullFlag)
			{
				status_exception::raise(
					Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
					Arg::Gds(isc_bad_default_value) <<
					Arg::Gds(isc_invalid_clause) << "default null not null");
			}
		}

		fieldDefinition.defaultValue = defaultValue;

		if (clause->collate.hasData())
			fieldDefinition.collationId = field->collationId;

		fieldDefinition.store(tdbb, transaction);

		// Define the field constraints.
		for (ObjectsArray<CreateDropConstraint>::iterator constraint(constraints.begin());
			 constraint != constraints.end();
			 ++constraint)
		{
			if (constraint->create->type != Constraint::TYPE_FK)
				constraint->create->columns.add(field->fld_name);
			defineConstraint(tdbb, dsqlScratch, transaction, constraint->name, *constraint->create);
		}
	}
	catch (const Exception&)
	{
		clearPermanentField(relation, permanent);
		throw;
	}

	clearPermanentField(relation, permanent);
}

// Define a DEFAULT clause. Return true for DEFAULT NULL.
bool RelationNode::defineDefault(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch,
	dsql_fld* /*field*/, ValueSourceClause* clause, string& source, BlrDebugWriter::BlrData& value)
{
	ValueExprNode* input = doDsqlPass(dsqlScratch, clause->value);

	// Generate the blr expression.

	dsqlScratch->getBlrData().clear();
	dsqlScratch->getDebugData().clear();
	dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

	GEN_expr(dsqlScratch, input);
	dsqlScratch->appendUChar(blr_eoc);

	// Generate the source text.
	source = clause->source;

	value.assign(dsqlScratch->getBlrData());

	return nodeIs<NullNode>(input);
}

// Make a constraint object from a legacy node.
void RelationNode::makeConstraint(thread_db* /*tdbb*/, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, AddConstraintClause* clause,
	ObjectsArray<CreateDropConstraint>& constraints, bool* notNull)
{
	MemoryPool& pool = dsqlScratch->getPool();

	switch (clause->constraintType)
	{
		case AddConstraintClause::CTYPE_NOT_NULL:
		case AddConstraintClause::CTYPE_PK:
			if (notNull && !*notNull)
			{
				*notNull = true;

				CreateDropConstraint& constraint = constraints.add();
				constraint.create = FB_NEW_POOL(pool) Constraint(pool);
				constraint.create->type = Constraint::TYPE_NOT_NULL;
				if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
					constraint.name = clause->name;
			}

			if (clause->constraintType == AddConstraintClause::CTYPE_NOT_NULL)
				break;
			// AddConstraintClause::CTYPE_PK falls into

		case AddConstraintClause::CTYPE_UNIQUE:
		{
			CreateDropConstraint& constraint = constraints.add();
			constraint.create = FB_NEW_POOL(pool) Constraint(pool);
			constraint.create->type = clause->constraintType == AddConstraintClause::CTYPE_PK ?
				Constraint::TYPE_PK : Constraint::TYPE_UNIQUE;
			constraint.name = clause->name;
			constraint.create->index = clause->index;
			if (constraint.create->index && constraint.create->index->name.isEmpty())
				constraint.create->index->name = constraint.name;
			constraint.create->columns = clause->columns;
			break;
		}

		case AddConstraintClause::CTYPE_FK:
		{
			CreateDropConstraint& constraint = constraints.add();
			constraint.create = FB_NEW_POOL(pool) Constraint(pool);
			constraint.create->type = Constraint::TYPE_FK;
			constraint.name = clause->name;
			constraint.create->columns = clause->columns;
			constraint.create->refRelation = clause->refRelation;
			constraint.create->refColumns = clause->refColumns;

			// If there is a referenced table name but no referenced field names, the
			// primary key of the referenced table designates the referenced fields.
			if (clause->refColumns.isEmpty())
			{
				Array<NestConst<FieldNode> > refColumns;
				METD_get_primary_key(transaction, clause->refRelation, refColumns);

				// If there is NEITHER an explicitly referenced field name, NOR does
				// the referenced table have a primary key to serve as the implicitly
				// referenced field, fail.
				if (refColumns.isEmpty())
				{
					// "REFERENCES table" without "(column)" requires PRIMARY
					// KEY on referenced table

					status_exception::raise(
						Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
						Arg::Gds(isc_dsql_command_err) <<
						Arg::Gds(isc_reftable_requires_pk));
				}
				else
				{
					const NestConst<FieldNode>* ptr = refColumns.begin();

					for (const NestConst<FieldNode>* const end = refColumns.end(); ptr != end; ++ptr)
						constraint.create->refColumns.add((*ptr)->dsqlName);
				}
			}

			if (constraint.create->refColumns.getCount() != constraint.create->columns.getCount())
			{
				// Foreign key field count does not match primary key.
				status_exception::raise(
					Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
					Arg::Gds(isc_dsql_command_err) <<
					Arg::Gds(isc_key_field_count_err));
			}

			// Define the foreign key index and the triggers that may be needed
			// for referential integrity action.
			constraint.create->index = clause->index;
			if (constraint.create->index && constraint.create->index->name.isEmpty())
				constraint.create->index->name = constraint.name;

			if (clause->refAction)
			{
				if (clause->refAction->updateAction != 0)
				{
					switch (clause->refAction->updateAction)
					{
						case RefActionClause::ACTION_CASCADE:
							constraint.create->refUpdateAction = RI_ACTION_CASCADE;
							defineUpdateCascadeTrigger(dsqlScratch, *constraint.create);
							break;

						case RefActionClause::ACTION_SET_DEFAULT:
							constraint.create->refUpdateAction = RI_ACTION_DEFAULT;
							defineSetDefaultTrigger(dsqlScratch, *constraint.create, true);
							break;

						case RefActionClause::ACTION_SET_NULL:
							constraint.create->refUpdateAction = RI_ACTION_NULL;
							defineSetNullTrigger(dsqlScratch, *constraint.create, true);
							break;

						default:
							fb_assert(0);
							// fall into

						case RefActionClause::ACTION_NONE:
							constraint.create->refUpdateAction = RI_ACTION_NONE;
							break;
					}
				}

				if (clause->refAction->deleteAction != 0)
				{
					switch (clause->refAction->deleteAction)
					{
						case RefActionClause::ACTION_CASCADE:
							constraint.create->refDeleteAction = RI_ACTION_CASCADE;
							defineDeleteCascadeTrigger(dsqlScratch, *constraint.create);
							break;

						case RefActionClause::ACTION_SET_DEFAULT:
							constraint.create->refDeleteAction = RI_ACTION_DEFAULT;
							defineSetDefaultTrigger(dsqlScratch, *constraint.create, false);
							break;

						case RefActionClause::ACTION_SET_NULL:
							constraint.create->refDeleteAction = RI_ACTION_NULL;
							defineSetNullTrigger(dsqlScratch, *constraint.create, false);
							break;

						default:
							fb_assert(0);
							// fall into

						case RefActionClause::ACTION_NONE:
							constraint.create->refDeleteAction = RI_ACTION_NONE;
							break;
					}
				}
			}

			break;
		}

		case AddConstraintClause::CTYPE_CHECK:
		{
			CreateDropConstraint& constraint = constraints.add();
			constraint.create = FB_NEW_POOL(pool) Constraint(pool);
			constraint.create->type = Constraint::TYPE_CHECK;
			constraint.name = clause->name;
			defineCheckConstraint(dsqlScratch, *constraint.create, clause->check);
			break;
		}
	}
}

// Define a constraint.
void RelationNode::defineConstraint(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, MetaName& constraintName, Constraint& constraint)
{
	if (constraintName.isEmpty())
		DYN_UTIL_generate_constraint_name(tdbb, constraintName);

	AutoCacheRequest request(tdbb, drq_s_rel_con, DYN_REQUESTS);
	MetaName referredIndexName;

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		CRT IN RDB$RELATION_CONSTRAINTS
	{
		CRT.RDB$INDEX_NAME.NULL = TRUE;

		strcpy(CRT.RDB$CONSTRAINT_NAME, constraintName.c_str());
		strcpy(CRT.RDB$RELATION_NAME, name.c_str());

		switch (constraint.type)
		{
			case Constraint::TYPE_CHECK:
				strcpy(CRT.RDB$CONSTRAINT_TYPE, CHECK_CNSTRT);
				break;

			case Constraint::TYPE_NOT_NULL:
				strcpy(CRT.RDB$CONSTRAINT_TYPE, NOT_NULL_CNSTRT);
				break;

			case Constraint::TYPE_PK:
				strcpy(CRT.RDB$CONSTRAINT_TYPE, PRIMARY_KEY);
				break;

			case Constraint::TYPE_UNIQUE:
				strcpy(CRT.RDB$CONSTRAINT_TYPE, UNIQUE_CNSTRT);
				break;

			case Constraint::TYPE_FK:
				strcpy(CRT.RDB$CONSTRAINT_TYPE, FOREIGN_KEY);
				break;

			default:
				fb_assert(false);
		}

		// These constraints require creation of an index.
		switch (constraint.type)
		{
			case Constraint::TYPE_PK:
			case Constraint::TYPE_UNIQUE:
			case Constraint::TYPE_FK:
			{
				CreateIndexNode::Definition definition;
				definition.relation = name;
				definition.type = (constraint.type == Constraint::TYPE_PK ?
					isc_dyn_def_primary_key :
					(constraint.type == Constraint::TYPE_FK ? isc_dyn_def_foreign_key : 0));
				definition.unique = constraint.type != Constraint::TYPE_FK;
				if (constraint.index->descending)
					definition.descending = true;
				definition.inactive = false;
				definition.columns = constraint.columns;
				definition.refRelation = constraint.refRelation;
				definition.refColumns = constraint.refColumns;

				CreateIndexNode::store(tdbb, transaction, constraint.index->name,
					definition, &referredIndexName);

				CRT.RDB$INDEX_NAME.NULL = FALSE;
				strcpy(CRT.RDB$INDEX_NAME, constraint.index->name.c_str());

				checkForeignKeyTempScope(tdbb, transaction, name, referredIndexName);

				// Check that we have references permissions on the table and
				// fields that the index:referredIndexName is on.
				SCL_check_index(tdbb, referredIndexName, 0, SCL_references);

				break;
			}
		}
	}
	END_STORE

	if (constraint.type == Constraint::TYPE_NOT_NULL)
	{
		fb_assert(constraint.columns.getCount() == 1);
		DYN_UTIL_store_check_constraints(tdbb, transaction, constraintName,
			*constraint.columns.begin());
	}

	// Define the automatically generated triggers.

	for (ObjectsArray<TriggerDefinition>::iterator trigger(constraint.triggers.begin());
		 trigger != constraint.triggers.end();
		 ++trigger)
	{
		trigger->store(tdbb, dsqlScratch, transaction);
		DYN_UTIL_store_check_constraints(tdbb, transaction, constraintName, trigger->name);
	}

	if (constraint.type == Constraint::TYPE_NOT_NULL || constraint.type == Constraint::TYPE_CHECK)
		return;

	// Make sure unique field names were specified for UNIQUE/PRIMARY/FOREIGN
	// All fields must have the NOT NULL attribute specified for UNIQUE/PRIMARY.

	request.reset(tdbb, drq_c_unq_nam2, DYN_REQUESTS);

	int allCount = 0;
	int uniqueCount = 0;
	ObjectsArray<MetaName> fieldList;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDS IN RDB$INDEX_SEGMENTS CROSS
		RFR IN RDB$RELATION_FIELDS CROSS
		FLX IN RDB$FIELDS
		WITH IDS.RDB$INDEX_NAME EQ constraint.index->name.c_str() AND
			 RFR.RDB$RELATION_NAME EQ name.c_str() AND
			 RFR.RDB$FIELD_NAME EQ IDS.RDB$FIELD_NAME AND
			 FLX.RDB$FIELD_NAME EQ RFR.RDB$FIELD_SOURCE
		REDUCED TO IDS.RDB$FIELD_NAME, IDS.RDB$INDEX_NAME, FLX.RDB$NULL_FLAG
		SORTED BY ASCENDING IDS.RDB$FIELD_NAME
	{
		if ((FLX.RDB$NULL_FLAG.NULL || !FLX.RDB$NULL_FLAG) &&
			(RFR.RDB$NULL_FLAG.NULL || !RFR.RDB$NULL_FLAG) &&
			constraint.type == Constraint::TYPE_PK)
		{
			// msg 123: "Field: %s not defined as NOT NULL - can't be used in PRIMARY KEY
			// constraint definition"
			status_exception::raise(Arg::PrivateDyn(123) << MetaName(RFR.RDB$FIELD_NAME));
		}

		++uniqueCount;
		fieldList.add() = IDS.RDB$FIELD_NAME;
	}
	END_FOR

	request.reset(tdbb, drq_n_idx_seg, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDS IN RDB$INDEX_SEGMENTS
		WITH IDS.RDB$INDEX_NAME EQ constraint.index->name.c_str()
	{
		++allCount;
	}
	END_FOR

	if (uniqueCount != allCount)
	{
		// msg 124: "A column name is repeated in the definition of constraint: %s"
		status_exception::raise(Arg::PrivateDyn(124) << constraintName);
	}

	if (constraint.type == Constraint::TYPE_FK)
	{
		request.reset(tdbb, drq_s_ref_con, DYN_REQUESTS);

		STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			REF IN RDB$REF_CONSTRAINTS
		{
			AutoCacheRequest request2(tdbb, drq_l_intg_con, DYN_REQUESTS);

			FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				CRT IN RDB$RELATION_CONSTRAINTS
				WITH CRT.RDB$INDEX_NAME EQ referredIndexName.c_str() AND
					 (CRT.RDB$CONSTRAINT_TYPE = PRIMARY_KEY OR
					  CRT.RDB$CONSTRAINT_TYPE = UNIQUE_CNSTRT)
			{
				fb_utils::exact_name_limit(CRT.RDB$CONSTRAINT_NAME, sizeof(CRT.RDB$CONSTRAINT_NAME));
				strcpy(REF.RDB$CONST_NAME_UQ, CRT.RDB$CONSTRAINT_NAME);
				strcpy(REF.RDB$CONSTRAINT_NAME, constraintName.c_str());

				REF.RDB$UPDATE_RULE.NULL = FALSE;
				strcpy(REF.RDB$UPDATE_RULE, constraint.refUpdateAction);

				REF.RDB$DELETE_RULE.NULL = FALSE;
				strcpy(REF.RDB$DELETE_RULE, constraint.refDeleteAction);
			}
			END_FOR
		}
		END_STORE
	}
	else
	{
		// For PRIMARY KEY/UNIQUE constraints, make sure same set of columns
		// is not used in another constraint of either type.

		request.reset(tdbb, drq_c_dup_con, DYN_REQUESTS);

		MetaName indexName;
		int listIndex = -1;
		bool found = false;

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			CRT IN RDB$RELATION_CONSTRAINTS CROSS
			IDS IN RDB$INDEX_SEGMENTS OVER RDB$INDEX_NAME
			WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND
				 CRT.RDB$CONSTRAINT_NAME NE constraintName.c_str() AND
				 (CRT.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY OR
				  CRT.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT)
			SORTED BY CRT.RDB$INDEX_NAME, DESCENDING IDS.RDB$FIELD_NAME
		{
			if (indexName != CRT.RDB$INDEX_NAME)
			{
				if (listIndex >= 0)
					found = false;

				if (found)
					break;

				listIndex = fieldList.getCount() - 1;
				indexName = CRT.RDB$INDEX_NAME;
				found = true;
			}

			if (listIndex >= 0)
			{
				if (fieldList[listIndex--] != IDS.RDB$FIELD_NAME)
					found = false;
			}
			else
				found = false;
		}
		END_FOR

		if (listIndex >= 0)
			found = false;

		if (found)
		{
			// msg 126: "Same set of columns cannot be used in more than one PRIMARY KEY
			// and/or UNIQUE constraint definition"
			status_exception::raise(Arg::PrivateDyn(126));
		}
	}
}

// Generate triggers to implement the CHECK clause, either at the field or table level.
void RelationNode::defineCheckConstraint(DsqlCompilerScratch* dsqlScratch, Constraint& constraint,
	BoolSourceClause* clause)
{
	// Create the INSERT trigger.
	defineCheckConstraintTrigger(dsqlScratch, constraint, clause, PRE_STORE_TRIGGER);

	// Create the UPDATE trigger.
	defineCheckConstraintTrigger(dsqlScratch, constraint, clause, PRE_MODIFY_TRIGGER);
}

// Define a check constraint trigger.
void RelationNode::defineCheckConstraintTrigger(DsqlCompilerScratch* dsqlScratch,
	Constraint& constraint, BoolSourceClause* clause, FB_UINT64 triggerType)
{
	thread_db* tdbb = JRD_get_thread_data();
	MemoryPool& pool = *tdbb->getDefaultPool();

	AutoSetRestore<bool> autoCheckConstraintTrigger(&dsqlScratch->checkConstraintTrigger, true);

	Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
	blrWriter.init(dsqlScratch);

	// Specify that the trigger should abort if the condition is not met.
	NestConst<CompoundStmtNode> actionNode = FB_NEW_POOL(pool) CompoundStmtNode(pool);

	ExceptionNode* exceptionNode = FB_NEW_POOL(pool) ExceptionNode(pool, CHECK_CONSTRAINT_EXCEPTION);
	exceptionNode->exception->type = ExceptionItem::GDS_CODE;

	actionNode->statements.add(exceptionNode);

	// Generate the trigger blr.

	dsqlScratch->getBlrData().clear();
	dsqlScratch->getDebugData().clear();

	dsqlScratch->appendUChar(blr_begin);

	// Create the "OLD" and "NEW" contexts for the trigger -- the new one could be a dummy
	// place holder to avoid resolving fields to that context but prevent relations referenced
	// in the trigger actions from referencing the predefined "1" context.

	dsqlScratch->resetContextStack();

	// CVC: I thought I could disable the OLD context here to avoid "ambiguous field name"
	// errors in pre_store and pre_modify triggers. Also, what sense can I make from NEW in
	// pre_delete? However, we clash at JRD with "no current record for fetch operation".

	dsqlNode->alias = OLD_CONTEXT_NAME;
	dsql_ctx* oldContext = PASS1_make_context(dsqlScratch, dsqlNode);
	oldContext->ctx_flags |= CTX_system;

	dsqlNode->alias = NEW_CONTEXT_NAME;
	dsql_ctx* newContext = PASS1_make_context(dsqlScratch, dsqlNode);
	newContext->ctx_flags |= CTX_system;

	// Generate the condition for firing the trigger.

	NotBoolNode* notNode = FB_NEW_POOL(pool) NotBoolNode(pool, clause->value);

	BoolExprNode* condition = notNode->dsqlPass(dsqlScratch);

	dsqlScratch->appendUChar(blr_if);
	GEN_expr(dsqlScratch, condition);

	// Generate the action statement for the trigger.
	Node::doDsqlPass(dsqlScratch, actionNode)->genBlr(dsqlScratch);

	dsqlScratch->appendUChar(blr_end);	// of if (as there's no ELSE branch)
	dsqlScratch->appendUChar(blr_end);	// of begin

	dsqlScratch->appendUChar(blr_eoc);	// end of the blr

	dsqlScratch->resetContextStack();

	// Move the blr to the constraint blrWriter.
	blrWriter.getBlrData().join(dsqlScratch->getBlrData());

	TriggerDefinition& trigger = constraint.triggers.add();
	trigger.systemFlag = fb_sysflag_check_constraint;
	trigger.relationName = name;
	trigger.type = triggerType;
	trigger.source = clause->source;
	trigger.blrData = blrWriter.getBlrData();
}

// Define "on delete|update set default" trigger (for referential integrity) along with its blr.
void RelationNode::defineSetDefaultTrigger(DsqlCompilerScratch* dsqlScratch,
	Constraint& constraint, bool onUpdate)
{
	fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
	fb_assert(constraint.columns.hasData());

	Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
	blrWriter.init(dsqlScratch);

	blrWriter.appendUChar(blr_begin);

	USHORT index = 0;

	for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin());
		 column != constraint.columns.end();
		 ++column, index += 2)
	{
		// If the FK table's column is NOT NULL without DEFAULT, we want to avoid the engine throwing a constraint
		// violation on our trigger's variable, so we declare two variables, one with blr_domain_type_of (don't use
		// DEFAULT and null flag) and another blr_domain_full with DEFAULT and null flag. We swallow the error when
		// transfering the value from the second to the first variable.

		// This could be better done with blr_default, but let's maintain backward compatible BLR for now.

		blrWriter.appendUChar(blr_dcl_variable);
		blrWriter.appendUShort(index);

		blrWriter.appendUChar(blr_column_name);
		blrWriter.appendUChar(blr_domain_type_of);
		blrWriter.appendNullString(0, name.c_str());
		blrWriter.appendNullString(0, column->c_str());

		blrWriter.appendUChar(blr_dcl_variable);
		blrWriter.appendUShort(index + 1);

		blrWriter.appendUChar(blr_column_name);
		blrWriter.appendUChar(blr_domain_full);
		blrWriter.appendNullString(0, name.c_str());
		blrWriter.appendNullString(0, column->c_str());

		blrWriter.appendUChar(blr_assignment);
		blrWriter.appendUChar(blr_null);
		blrWriter.appendUChar(blr_variable);
		blrWriter.appendUShort(index);

		blrWriter.appendUChar(blr_block);	// 1

		blrWriter.appendUChar(blr_begin);	// 2

		blrWriter.appendUChar(blr_init_variable);
		blrWriter.appendUShort(index + 1);

		blrWriter.appendUChar(blr_assignment);
		blrWriter.appendUChar(blr_variable);
		blrWriter.appendUShort(index + 1);
		blrWriter.appendUChar(blr_variable);
		blrWriter.appendUShort(index);

		blrWriter.appendUChar(blr_end);		// 2

		blrWriter.appendUChar(blr_error_handler);
		blrWriter.appendUShort(1);
		blrWriter.appendUChar(blr_default_code);
		blrWriter.appendUChar(blr_begin);	// 3
		blrWriter.appendUChar(blr_end);		// 3

		blrWriter.appendUChar(blr_end);		// 1
	}

	generateUnnamedTriggerBeginning(constraint, onUpdate, blrWriter);

	index = 0;

	for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin());
		 column != constraint.columns.end();
		 ++column, index += 2)
	{
		blrWriter.appendUChar(blr_assignment);

		blrWriter.appendUChar(blr_variable);
		blrWriter.appendUShort(index);

		// The context for the foreign key relation.
		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(2);
		blrWriter.appendNullString(0, column->c_str());
	}

	blrWriter.appendUChar(blr_end);

	if (onUpdate)
		blrWriter.appendUCharRepeated(blr_end, 3);

	blrWriter.appendUChar(blr_end);

	blrWriter.appendUChar(blr_eoc);	// end of the blr

	TriggerDefinition& trigger = constraint.triggers.add();
	trigger.systemFlag = fb_sysflag_referential_constraint;
	trigger.fkTrigger = true;
	trigger.relationName = constraint.refRelation;
	trigger.type = (onUpdate ? POST_MODIFY_TRIGGER : POST_ERASE_TRIGGER);
	trigger.blrData = blrWriter.getBlrData();
}

// Define "on delete/update set null" trigger (for referential integrity).
// The trigger blr is the same for both the delete and update cases. Only difference is its
// TRIGGER_TYPE (ON DELETE or ON UPDATE).
// When onUpdate parameter == true is an update trigger.
void RelationNode::defineSetNullTrigger(DsqlCompilerScratch* dsqlScratch, Constraint& constraint,
	bool onUpdate)
{
	fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
	fb_assert(constraint.columns.hasData());

	Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
	blrWriter.init(dsqlScratch);

	generateUnnamedTriggerBeginning(constraint, onUpdate, blrWriter);

	for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin());
		 column != constraint.columns.end();
		 ++column)
	{
		blrWriter.appendUChar(blr_assignment);
		blrWriter.appendUChar(blr_null);
		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(2);
		blrWriter.appendNullString(0, column->c_str());
	}

	blrWriter.appendUChar(blr_end);

	if (onUpdate)
		blrWriter.appendUCharRepeated(blr_end, 3);

	blrWriter.appendUChar(blr_eoc);	// end of the blr

	TriggerDefinition& trigger = constraint.triggers.add();
	trigger.systemFlag = fb_sysflag_referential_constraint;
	trigger.fkTrigger = true;
	trigger.relationName = constraint.refRelation;
	trigger.type = (onUpdate ? POST_MODIFY_TRIGGER : POST_ERASE_TRIGGER);
	trigger.blrData = blrWriter.getBlrData();
}

// Define "on delete cascade" trigger (for referential integrity) along with the trigger blr.
void RelationNode::defineDeleteCascadeTrigger(DsqlCompilerScratch* dsqlScratch,
	Constraint& constraint)
{
	fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
	fb_assert(constraint.columns.hasData());

	Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
	blrWriter.init(dsqlScratch);

	blrWriter.appendUChar(blr_for);
	blrWriter.appendUChar(blr_rse);

	// The context for the prim. key relation.
	blrWriter.appendUChar(1);
	blrWriter.appendUChar(blr_relation);
	blrWriter.appendNullString(0, name.c_str());
	// The context for the foreign key relation.
	blrWriter.appendUChar(2);

	// Generate the blr for: foreign_key == primary_key.
	stuffMatchingBlr(constraint, blrWriter);

	blrWriter.appendUChar(blr_erase);
	blrWriter.appendUChar(2);

	blrWriter.appendUChar(blr_eoc);	// end of the blr

	TriggerDefinition& trigger = constraint.triggers.add();
	trigger.systemFlag = fb_sysflag_referential_constraint;
	trigger.fkTrigger = true;
	trigger.relationName = constraint.refRelation;
	trigger.type = POST_ERASE_TRIGGER;
	trigger.blrData = blrWriter.getBlrData();
}

// Define "on update cascade" trigger (for referential integrity) along with the trigger blr.
void RelationNode::defineUpdateCascadeTrigger(DsqlCompilerScratch* dsqlScratch,
	Constraint& constraint)
{
	fb_assert(constraint.columns.getCount() == constraint.refColumns.getCount());
	fb_assert(constraint.columns.hasData());

	Constraint::BlrWriter& blrWriter = constraint.blrWritersHolder.add();
	blrWriter.init(dsqlScratch);

	generateUnnamedTriggerBeginning(constraint, true, blrWriter);

	for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin()),
			refColumn(constraint.refColumns.begin());
		 column != constraint.columns.end();
		 ++column, ++refColumn)
	{
		blrWriter.appendUChar(blr_assignment);

		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(1);
		blrWriter.appendNullString(0, refColumn->c_str());

		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(2);
		blrWriter.appendNullString(0, column->c_str());
	}

	blrWriter.appendUCharRepeated(blr_end, 4);
	blrWriter.appendUChar(blr_eoc);	// end of the blr

	TriggerDefinition& trigger = constraint.triggers.add();
	trigger.systemFlag = fb_sysflag_referential_constraint;
	trigger.fkTrigger = true;
	trigger.relationName = constraint.refRelation;
	trigger.type = POST_MODIFY_TRIGGER;
	trigger.blrData = blrWriter.getBlrData();
}

// Common code factored out.
void RelationNode::generateUnnamedTriggerBeginning(Constraint& constraint, bool onUpdate,
	BlrDebugWriter& blrWriter)
{
	// For ON UPDATE TRIGGER only: generate the trigger firing condition:
	// If prim_key.old_value != prim_key.new value.
	// Note that the key could consist of multiple columns.

	if (onUpdate)
	{
		stuffTriggerFiringCondition(constraint, blrWriter);
		blrWriter.appendUCharRepeated(blr_begin, 2);
	}

	blrWriter.appendUChar(blr_for);
	blrWriter.appendUChar(blr_rse);

	// The context for the prim. key relation.
	blrWriter.appendUChar(1);
	blrWriter.appendUChar(blr_relation);
	blrWriter.appendNullString(0, name.c_str());
	// The context for the foreign key relation.
	blrWriter.appendUChar(2);

	// Generate the blr for: foreign_key == primary_key.
	stuffMatchingBlr(constraint, blrWriter);

	blrWriter.appendUChar(blr_modify);
	blrWriter.appendUChar(2);
	blrWriter.appendUChar(2);
	blrWriter.appendUChar(blr_begin);
}

// The defaultBlr passed is of the form:
//     blr_version4 blr_literal ..... blr_eoc.
// Strip the blr_version4 and blr_eoc verbs and stuff the remaining blr in the blr stream in the
// statement.
void RelationNode::stuffDefaultBlr(const ByteChunk& defaultBlr, BlrDebugWriter& blrWriter)
{
	fb_assert(defaultBlr.length > 2 && defaultBlr.data[defaultBlr.length - 1] == blr_eoc);
	fb_assert(*defaultBlr.data == blr_version4 || *defaultBlr.data == blr_version5);

	blrWriter.appendBytes(defaultBlr.data + 1, defaultBlr.length - 2);
}

// Generate blr to express: foreign_key == primary_key
// ie., for_key.column_1 = prim_key.column_1 and
//      for_key.column_2 = prim_key.column_2 and ....  so on.
void RelationNode::stuffMatchingBlr(Constraint& constraint, BlrDebugWriter& blrWriter)
{
	// count of foreign key columns
	fb_assert(constraint.refColumns.getCount() == constraint.columns.getCount());
	fb_assert(constraint.refColumns.getCount() != 0);

	blrWriter.appendUChar(blr_boolean);

	size_t numFields = 0;

	for (ObjectsArray<MetaName>::const_iterator column(constraint.columns.begin()),
			refColumn(constraint.refColumns.begin());
		 column != constraint.columns.end();
		 ++column, ++refColumn, ++numFields)
	{
		if (numFields + 1 < constraint.columns.getCount())
			blrWriter.appendUChar(blr_and);

		blrWriter.appendUChar(blr_eql);

		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(2);
		blrWriter.appendNullString(0, column->c_str());
		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(0);
		blrWriter.appendNullString(0, refColumn->c_str());
	};

	blrWriter.appendUChar(blr_end);
}

// Generate blr to express: if (old.primary_key != new.primary_key).
// Do a column by column comparison.
void RelationNode::stuffTriggerFiringCondition(const Constraint& constraint, BlrDebugWriter& blrWriter)
{
	blrWriter.appendUChar(blr_if);

	size_t numFields = 0;

	for (ObjectsArray<MetaName>::const_iterator column(constraint.refColumns.begin());
		 column != constraint.refColumns.end();
		 ++column, ++numFields)
	{
		if (numFields + 1 < constraint.refColumns.getCount())
			blrWriter.appendUChar(blr_or);

		blrWriter.appendUChar(blr_neq);

		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(0);
		blrWriter.appendNullString(0, column->c_str());
		blrWriter.appendUChar(blr_field);
		blrWriter.appendUChar(1);
		blrWriter.appendNullString(0, column->c_str());
	}
}


void RelationNode::addToPublication(thread_db* tdbb,
									jrd_tra* transaction,
									const MetaName& tableName,
									const MetaName& pubName)
{
	AutoCacheRequest request(tdbb, drq_s_pub_tab, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PTAB IN RDB$PUBLICATION_TABLES
	{
		strcpy(PTAB.RDB$PUBLICATION_NAME, pubName.c_str());
		PTAB.RDB$PUBLICATION_NAME.NULL = FALSE;

		strcpy(PTAB.RDB$TABLE_NAME, tableName.c_str());
		PTAB.RDB$TABLE_NAME.NULL = FALSE;
	}
	END_STORE
}


void RelationNode::dropFromPublication(thread_db* tdbb,
									   jrd_tra* transaction,
									   const MetaName& tableName,
									   const MetaName& pubName)
{
	AutoCacheRequest request(tdbb, drq_e_pub_tab, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PTAB IN RDB$PUBLICATION_TABLES
		WITH PTAB.RDB$PUBLICATION_NAME EQ pubName.c_str()
			AND PTAB.RDB$TABLE_NAME EQ tableName.c_str()
	{
		ERASE PTAB;
	}
	END_FOR
}


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


string CreateRelationNode::internalPrint(NodePrinter& printer) const
{
	RelationNode::internalPrint(printer);

	NODE_PRINT(printer, externalFile);
	NODE_PRINT(printer, relationType);

	return "CreateRelationNode";
}

void CreateRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_create_access(tdbb, SCL_object_table);
}

void CreateRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	saveRelation(tdbb, dsqlScratch, name, false, true);

	if (externalFile)
	{
		fb_assert(dsqlScratch->relation);
		dsqlScratch->relation->rel_flags |= REL_external;
	}

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_TABLE,
		name, NULL);

	DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_relation);

	fb_assert(relationType.specified);

	checkRelationTempScope(tdbb, transaction, name, relationType.value);

	AutoCacheRequest request(tdbb, drq_s_rels2, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		REL IN RDB$RELATIONS
	{
		strcpy(REL.RDB$RELATION_NAME, name.c_str());
		REL.RDB$SYSTEM_FLAG = 0;
		REL.RDB$FLAGS = REL_sql;
		REL.RDB$RELATION_TYPE = relationType.value;

		if (ssDefiner.specified)
		{
			REL.RDB$SQL_SECURITY.NULL = FALSE;
			REL.RDB$SQL_SECURITY = ssDefiner.value ? FB_TRUE : FB_FALSE;
		}
		else
			REL.RDB$SQL_SECURITY.NULL = TRUE;

		REL.RDB$VIEW_BLR.NULL = TRUE;
		REL.RDB$VIEW_SOURCE.NULL = TRUE;
		REL.RDB$EXTERNAL_FILE.NULL = TRUE;

		if (externalFile)
		{
			if (externalFile->length() >= sizeof(REL.RDB$EXTERNAL_FILE))
				status_exception::raise(Arg::Gds(isc_dyn_name_longer));

			if (!(tdbb->tdbb_flags & TDBB_replicator) &&
				ISC_check_if_remote(externalFile->c_str(), false))
			{
				status_exception::raise(Arg::PrivateDyn(163));
			}

			REL.RDB$EXTERNAL_FILE.NULL = FALSE;
			strcpy(REL.RDB$EXTERNAL_FILE, externalFile->c_str());
			REL.RDB$RELATION_TYPE = rel_external;
		}
	}
	END_STORE

	bool replicationEnabled;

	if (replicationState.specified)
		replicationEnabled = replicationState.value;
	else
	{
		// Apply the default replication state to the table being created

		AutoCacheRequest request2(tdbb, drq_l_pub_mode, DYN_REQUESTS);

		FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			PUB IN RDB$PUBLICATIONS
			WITH PUB.RDB$PUBLICATION_NAME EQ DEFAULT_PUBLICATION
		{
			replicationEnabled = (PUB.RDB$AUTO_ENABLE != 0);
		}
		END_FOR
	}

	if (replicationEnabled)
	{
		// Add table to the default publication
		RelationNode::addToPublication(tdbb, transaction, name, DEFAULT_PUBLICATION);
	}

	storePrivileges(tdbb, transaction, name, obj_relation, ALL_PRIVILEGES);

	ObjectsArray<CreateDropConstraint> constraints;
	const ObjectsArray<MetaName>* pkCols = findPkColumns();
	SSHORT position = 0;

	for (NestConst<Clause>* i = clauses.begin(); i != clauses.end(); ++i)
	{
		switch ((*i)->type)
		{
			case Clause::TYPE_ADD_COLUMN:
				defineField(tdbb, dsqlScratch, transaction,
					static_cast<AddColumnClause*>(i->getObject()), position, pkCols);
				++position;
				break;

			case Clause::TYPE_ADD_CONSTRAINT:
				makeConstraint(tdbb, dsqlScratch, transaction,
					static_cast<AddConstraintClause*>(i->getObject()), constraints);
				break;

			default:
				fb_assert(false);
				break;
		}
	}

	for (ObjectsArray<CreateDropConstraint>::iterator constraint(constraints.begin());
		 constraint != constraints.end();
		 ++constraint)
	{
		defineConstraint(tdbb, dsqlScratch, transaction, constraint->name, *constraint->create);
	}

	dsqlScratch->relation->rel_flags &= ~REL_creating;

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_TABLE,
		name, NULL);

	savePoint.release();	// everything is ok

	// Update DSQL cache
	METD_drop_relation(transaction, name);
	MET_dsql_cache_release(tdbb, SYM_relation, name);
}

// Starting from the elements in a table definition, locate the PK columns if given in a
// separate table constraint declaration.
const ObjectsArray<MetaName>* CreateRelationNode::findPkColumns()
{
	for (const NestConst<Clause>* i = clauses.begin(); i != clauses.end(); ++i)
	{
		if ((*i)->type == Clause::TYPE_ADD_CONSTRAINT)
		{
			const AddConstraintClause* clause = static_cast<const AddConstraintClause*>(i->getObject());

			if (clause->constraintType == AddConstraintClause::CTYPE_PK)
				return &clause->columns;
		}
	}

	return NULL;
}


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


string AlterRelationNode::internalPrint(NodePrinter& printer) const
{
	RelationNode::internalPrint(printer);
	return "AlterRelationNode";
}

void AlterRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	SCL_check_relation(tdbb, &dscName, SCL_alter);
}

void AlterRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	saveRelation(tdbb, dsqlScratch, name, false, false);

	dsql_rel* relation;
	relation = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, name);

	if (!relation || (relation->rel_flags & REL_view))
	{
		status_exception::raise(
			Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
			Arg::Gds(isc_dsql_command_err) <<
			Arg::Gds(isc_dsql_table_not_found) << name);
	}

	if (!dsqlScratch->relation)
	{
		//// TODO: <Missing arg #1 - possibly status vector overflow>
		/***
		char linecol[64];
		sprintf(linecol, "At line %d, column %d.", (int) dsqlNode->line, (int) dsqlNode->column);
		***/

		status_exception::raise(
			Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
			Arg::Gds(isc_dsql_relation_err) <<
			Arg::Gds(isc_random) << name /***<<
			Arg::Gds(isc_random) << linecol***/);
	}

	// If there is an error, get rid of the cached data.

	try
	{
		// run all statements under savepoint control
		AutoSavePoint savePoint(tdbb, transaction);

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_TABLE,
			name, NULL);

		ObjectsArray<CreateDropConstraint> constraints;

		for (NestConst<Clause>* i = clauses.begin(); i != clauses.end(); ++i)
		{
			switch ((*i)->type)
			{
				case Clause::TYPE_ADD_COLUMN:
					defineField(tdbb, dsqlScratch, transaction,
						static_cast<AddColumnClause*>(i->getObject()), -1, NULL);
					break;

				case Clause::TYPE_ALTER_COL_TYPE:
					modifyField(tdbb, dsqlScratch, transaction,
						static_cast<AlterColTypeClause*>(i->getObject()));
					break;

				case Clause::TYPE_ALTER_COL_NAME:
				{
					const AlterColNameClause* clause =
						static_cast<const AlterColNameClause*>(i->getObject());
					AutoRequest request;
					bool found = false;

					FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
						RFL IN RDB$RELATION_FIELDS
						WITH RFL.RDB$FIELD_NAME EQ clause->fromName.c_str() AND
							 RFL.RDB$RELATION_NAME EQ name.c_str()
					{
						found = true;

						MODIFY RFL
							checkViewDependency(tdbb, transaction, name, clause->fromName);
							checkSpTrigDependency(tdbb, transaction, name, clause->fromName);

							if (!fieldExists(tdbb, transaction, name, clause->toName))
							{
								strcpy(RFL.RDB$FIELD_NAME, clause->toName.c_str());
								AlterDomainNode::modifyLocalFieldIndex(tdbb, transaction, name,
									clause->fromName, clause->toName);
							}
							else
							{
								// msg 205: Cannot rename field %s to %s.  A field with that name
								// already exists in table %s.
								status_exception::raise(
									Arg::PrivateDyn(205) << clause->fromName << clause->toName << name);
							}
						END_MODIFY
					}
					END_FOR

					if (!found)
					{
						// msg 176: "column %s does not exist in table/view %s"
						status_exception::raise(Arg::PrivateDyn(176) << clause->fromName << name);
					}

					AutoRequest request2;

					FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
						RCL IN RDB$RELATION_CONSTRAINTS CROSS
						CHK IN RDB$CHECK_CONSTRAINTS
						WITH RCL.RDB$RELATION_NAME EQ name.c_str() AND
							 RCL.RDB$CONSTRAINT_TYPE EQ NOT_NULL_CNSTRT AND
							 CHK.RDB$CONSTRAINT_NAME EQ RCL.RDB$CONSTRAINT_NAME AND
							 CHK.RDB$TRIGGER_NAME EQ clause->fromName.c_str()
					{
						MODIFY CHK
							strcpy(CHK.RDB$TRIGGER_NAME, clause->toName.c_str());
						END_MODIFY
					}
					END_FOR

					break;
				}

				case Clause::TYPE_ALTER_COL_NULL:
				{
					const AlterColNullClause* clause =
						static_cast<const AlterColNullClause*>(i->getObject());

					AutoRequest request;
					bool found = false;

					FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
						RFL IN RDB$RELATION_FIELDS
						WITH RFL.RDB$FIELD_NAME EQ clause->name.c_str() AND
							 RFL.RDB$RELATION_NAME EQ name.c_str()
					{
						found = true;

						MODIFY RFL
						{
							if (!clause->notNullFlag && !RFL.RDB$GENERATOR_NAME.NULL)
							{
								// msg 274: Identity column @1 of table @2 cannot be changed to NULLable
								status_exception::raise(Arg::PrivateDyn(274) << clause->name << name);
							}

							if (clause->notNullFlag)
							{
								RFL.RDB$NULL_FLAG.NULL = FALSE;
								RFL.RDB$NULL_FLAG = TRUE;

								MetaName dummyName;
								Constraint nullConstraint(*tdbb->getDefaultPool());
								nullConstraint.type = Constraint::TYPE_NOT_NULL;
								nullConstraint.columns.add(clause->name);
								defineConstraint(tdbb, dsqlScratch, transaction, dummyName,
									nullConstraint);
							}
							else
							{
								RFL.RDB$NULL_FLAG.NULL = TRUE;

								AutoRequest request2;

								FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
									RCL IN RDB$RELATION_CONSTRAINTS CROSS
									CHK IN RDB$CHECK_CONSTRAINTS
									WITH RCL.RDB$RELATION_NAME EQ name.c_str() AND
										 RCL.RDB$CONSTRAINT_TYPE EQ NOT_NULL_CNSTRT AND
										 CHK.RDB$CONSTRAINT_NAME EQ RCL.RDB$CONSTRAINT_NAME AND
										 CHK.RDB$TRIGGER_NAME EQ clause->name.c_str()
								{
									// ASF: Record in RDB$CHECK_CONSTRAINTS is deleted by a
									// system trigger.
									ERASE RCL;
								}
								END_FOR
							}
						}
						END_MODIFY
					}
					END_FOR

					if (!found)
					{
						// msg 176: "column %s does not exist in table/view %s"
						status_exception::raise(Arg::PrivateDyn(176) << clause->name << name);
					}

					break;
				}

				case Clause::TYPE_ALTER_COL_POS:
				{
					const AlterColPosClause* clause =
						static_cast<const AlterColPosClause*>(i->getObject());
					// CVC: Since now the parser accepts pos=1..N, let's subtract one here.
					const SSHORT pos = clause->newPos - 1;

					modifyLocalFieldPosition(tdbb, transaction, name, clause->name, pos);

					break;
				}

				case Clause::TYPE_DROP_COLUMN:
				{
					// Fix for bug 8054:
					// [CASCADE | RESTRICT] syntax is available in IB4.5, but not
					// required until v5.0.
					//
					// Option CASCADE causes an error: unsupported DSQL construct.
					// Option RESTRICT is default behaviour.

					const DropColumnClause* clause =
						static_cast<const DropColumnClause*>(i->getObject());

					if (clause->cascade)
					{
						// Unsupported DSQL construct
						status_exception::raise(
							Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
							Arg::Gds(isc_dsql_command_err) <<
							Arg::Gds(isc_dsql_construct_err));
					}

					deleteLocalField(tdbb, transaction, name, clause->name);
					break;
				}

				case Clause::TYPE_ADD_CONSTRAINT:
					makeConstraint(tdbb, dsqlScratch, transaction,
						static_cast<AddConstraintClause*>(i->getObject()), constraints);
					break;

				case Clause::TYPE_DROP_CONSTRAINT:
				{
					CreateDropConstraint& dropConstraint = constraints.add();
					dropConstraint.name =
						static_cast<const DropConstraintClause*>(i->getObject())->name;
					break;
				}

				case Clause::TYPE_ALTER_SQL_SECURITY:
				{
					AutoRequest request;

					FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
						REL IN RDB$RELATIONS
						WITH REL.RDB$RELATION_NAME EQ name.c_str()
					{
						MODIFY REL
						{
							if (ssDefiner.specified)
							{
								REL.RDB$SQL_SECURITY.NULL = FALSE;
								REL.RDB$SQL_SECURITY = ssDefiner.value ? FB_TRUE : FB_FALSE;
							}
							else
								REL.RDB$SQL_SECURITY.NULL = TRUE;
						}
						END_MODIFY
					}
					END_FOR

					break;
				}

				case Clause::TYPE_ALTER_PUBLICATION:
				{
					fb_assert(replicationState.specified);

					if (replicationState.value)
					{
						// Add table to the publication

						try
						{
							RelationNode::addToPublication(tdbb, transaction,
														   name, DEFAULT_PUBLICATION);
						}
						catch (const status_exception& ex)
						{
							if (ex.value()[1] != isc_unique_key_violation)
								throw;

							// Ignore duplicated records
							fb_utils::init_status(tdbb->tdbb_status_vector);
						}
					}
					else
					{
						// Drop table from the publication

						RelationNode::dropFromPublication(tdbb, transaction,
														  name, DEFAULT_PUBLICATION);
					}

					break;
				}

				default:
					fb_assert(false);
					break;
			}
		}

		for (ObjectsArray<CreateDropConstraint>::iterator constraint(constraints.begin());
			 constraint != constraints.end();
			 ++constraint)
		{
			if (constraint->create)
			{
				defineConstraint(tdbb, dsqlScratch, transaction,
					constraint->name, *constraint->create);
			}
			else
			{
				AutoCacheRequest request(tdbb, drq_e_rel_con, DYN_REQUESTS);
				bool found = false;

				FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
					RC IN RDB$RELATION_CONSTRAINTS
					WITH RC.RDB$CONSTRAINT_NAME EQ constraint->name.c_str() AND
						 RC.RDB$RELATION_NAME EQ name.c_str()
				{
					found = true;
					ERASE RC;
				}
				END_FOR

				if (!found)
				{
					// msg 130: "CONSTRAINT %s does not exist."
					status_exception::raise(Arg::PrivateDyn(130) << constraint->name);
				}
			}
		}

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_TABLE,
			name, NULL);

		savePoint.release();	// everything is ok

		// Update DSQL cache
		METD_drop_relation(transaction, name);
		MET_dsql_cache_release(tdbb, SYM_relation, name);
	}
	catch (const Exception&)
	{
		METD_drop_relation(transaction, name);
		dsqlScratch->relation = NULL;
		throw;
	}
}

// Modify a field, as part of an alter table statement.
//
// If there are dependencies on the field, abort the operation
// unless the dependency is an index.  In this case, rebuild the
// index once the operation has completed.
//
// If the original datatype of the field was a domain:
//    if the new type is a domain, just make the change to the new domain
//    if it exists
//
//    if the new type is a base type, just make the change
//
// If the original datatype of the field was a base type:
//    if the new type is a base type, just make the change
//
//    if the new type is a domain, make the change to the field
//    definition and remove the entry for RDB$FIELD_SOURCE from the original
//    field.  In other words ... clean up after ourselves
//
// The following conversions are not allowed:
//       Blob to anything
//       Array to anything
//       Date to anything
//       Char to any numeric
//       Varchar to any numeric
//       Anything to Blob
//       Anything to Array
void AlterRelationNode::modifyField(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction, AlterColTypeClause* clause)
{
	Attachment* const attachment = transaction->tra_attachment;

	dsql_fld* field = clause->field;

	// Add the field to the relation being defined for parsing purposes.
	bool permanent = false;
	dsql_rel* relation = dsqlScratch->relation;

	if (relation)
	{
		if (!(relation->rel_flags & REL_new_relation))
		{
			dsql_fld* permField = FB_NEW_POOL(dsqlScratch->getAttachment()->dbb_pool)
				dsql_fld(dsqlScratch->getAttachment()->dbb_pool);
			*permField = *field;

			field = permField;
			permanent = true;
		}

		field->fld_next = relation->rel_fields;
		relation->rel_fields = field;
	}

	try
	{
		bool found = false;
		AutoRequest request;

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			RFR IN RDB$RELATION_FIELDS CROSS
			REL IN RDB$RELATIONS CROSS
			FLD IN RDB$FIELDS
			WITH REL.RDB$RELATION_NAME = RFR.RDB$RELATION_NAME AND
				 FLD.RDB$FIELD_NAME = RFR.RDB$FIELD_SOURCE AND
				 RFR.RDB$RELATION_NAME = name.c_str() AND
				 RFR.RDB$FIELD_NAME = field->fld_name.c_str()
		{
			found = true;

			bool isView = !REL.RDB$VIEW_BLR.NULL;

			if (!isView && (!FLD.RDB$COMPUTED_BLR.NULL != (clause->computed != NULL)))
			{
				// Cannot add or remove COMPUTED from column @1
				status_exception::raise(Arg::PrivateDyn(249) << field->fld_name);
			}

			dyn_fld origDom;

			DSC_make_descriptor(&origDom.dyn_dsc, FLD.RDB$FIELD_TYPE, FLD.RDB$FIELD_SCALE,
				FLD.RDB$FIELD_LENGTH, FLD.RDB$FIELD_SUB_TYPE, FLD.RDB$CHARACTER_SET_ID,
				FLD.RDB$COLLATION_ID);

			origDom.dyn_fld_name = field->fld_name;
			origDom.dyn_charbytelen = FLD.RDB$FIELD_LENGTH;
			origDom.dyn_dtype = FLD.RDB$FIELD_TYPE;
			origDom.dyn_precision = FLD.RDB$FIELD_PRECISION;
			origDom.dyn_sub_type = FLD.RDB$FIELD_SUB_TYPE;
			origDom.dyn_charlen = FLD.RDB$CHARACTER_LENGTH;
			origDom.dyn_collation = FLD.RDB$COLLATION_ID;
			origDom.dyn_null_flag = !FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG != 0;
			origDom.dyn_fld_source = RFR.RDB$FIELD_SOURCE;

			// If the original field type is an array, force its blr type to blr_blob.
			const bool hasDimensions = FLD.RDB$DIMENSIONS != 0;
			if (hasDimensions)
				origDom.dyn_dtype = blr_blob;

			const bool wasInternalDomain = fb_utils::implicit_domain(origDom.dyn_fld_source.c_str()) &&
				RFR.RDB$BASE_FIELD.NULL;
			string computedSource;
			BlrDebugWriter::BlrData computedValue;

			if (clause->computed)
			{
				field->flags |= FLD_computed;

				defineComputed(dsqlScratch, dsqlNode, field, clause->computed, computedSource,
					computedValue);
			}

			if (clause->defaultValue)
			{
				MODIFY RFR
					if (!RFR.RDB$GENERATOR_NAME.NULL)
					{
						// msg 275: Identity column @1 of table @2 cannot have default value
						status_exception::raise(Arg::PrivateDyn(275) << field->fld_name << name);
					}

					if (hasDimensions)
					{
						// msg 225: "Default value is not allowed for array type in field %s"
						status_exception::raise(Arg::PrivateDyn(225) << field->fld_name);
					}

					if (clause->computed)
					{
						// msg 233: "Local column %s is computed, cannot set a default value"
						status_exception::raise(Arg::PrivateDyn(233) << field->fld_name);
					}

					string defaultSource;
					BlrDebugWriter::BlrData defaultValue;

					defineDefault(tdbb, dsqlScratch, field, clause->defaultValue,
						defaultSource, defaultValue);

					RFR.RDB$DEFAULT_SOURCE.NULL = FALSE;
					attachment->storeMetaDataBlob(tdbb, transaction,
						&RFR.RDB$DEFAULT_SOURCE, defaultSource);

					RFR.RDB$DEFAULT_VALUE.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction,
						&RFR.RDB$DEFAULT_VALUE, defaultValue);
				END_MODIFY
			}
			else if (clause->dropDefault)
			{
				MODIFY RFR
					if (RFR.RDB$DEFAULT_VALUE.NULL)
					{
						if (FLD.RDB$DEFAULT_VALUE.NULL)
						{
							// msg 229: "Local column %s doesn't have a default"
							status_exception::raise(Arg::PrivateDyn(229) << field->fld_name);
						}
						else
						{
							// msg 230: "Local column %s default belongs to domain %s"
							status_exception::raise(
								Arg::PrivateDyn(230) <<
									field->fld_name << MetaName(FLD.RDB$FIELD_NAME));
						}
					}
					else
					{
						RFR.RDB$DEFAULT_SOURCE.NULL = TRUE;
						RFR.RDB$DEFAULT_VALUE.NULL = TRUE;
					}
				END_MODIFY
			}
			else if (clause->dropIdentity)
			{
				if (RFR.RDB$GENERATOR_NAME.NULL)
				{
					// msg 285: "Column @1 is not an identity column"
					status_exception::raise(Arg::PrivateDyn(285) << field->fld_name);
				}

				DropSequenceNode::deleteIdentity(tdbb, transaction, RFR.RDB$GENERATOR_NAME);

				MODIFY RFR
					RFR.RDB$GENERATOR_NAME.NULL = TRUE;
					RFR.RDB$IDENTITY_TYPE.NULL = TRUE;
				END_MODIFY
			}
			else if (clause->identityOptions)
			{
				bool found = false;
				AutoRequest request2;

				FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
					GEN IN RDB$GENERATORS
					WITH GEN.RDB$GENERATOR_NAME EQ RFR.RDB$GENERATOR_NAME
				{
					const SLONG id = GEN.RDB$GENERATOR_ID;
					const MetaName genName(RFR.RDB$GENERATOR_NAME);

					if (clause->identityOptions->restart)
					{
						const SINT64 val =
							clause->identityOptions->startValue
								.orElse(!GEN.RDB$INITIAL_VALUE.NULL ? GEN.RDB$INITIAL_VALUE : 0) -
							clause->identityOptions->increment
								.orElse(!GEN.RDB$GENERATOR_INCREMENT.NULL ? GEN.RDB$GENERATOR_INCREMENT : 1);

						transaction->getGenIdCache()->put(id, val);
					}

					if (clause->identityOptions->type.specified)
					{
						MODIFY RFR
							RFR.RDB$IDENTITY_TYPE = clause->identityOptions->type.value;
						END_MODIFY
					}

					if (clause->identityOptions->increment.specified)
					{
						if (clause->identityOptions->increment.value == 0)
						{
							status_exception::raise(Arg::Gds(isc_dyn_cant_use_zero_inc_ident) <<
								Arg::Str(field->fld_name) <<
								Arg::Str(name));
						}

						MET_update_generator_increment(tdbb, id,
							clause->identityOptions->increment.value);
					}

					dsc desc;
					desc.makeText((USHORT) genName.length(), ttype_metadata,
						(UCHAR*) genName.c_str());
					DFW_post_work(transaction, dfw_set_generator, &desc, id);

					found = true;
				}
				END_FOR

				if (!found)
				{
					// msg 285: "Column @1 is not an identity column"
					status_exception::raise(Arg::PrivateDyn(285) << field->fld_name);
				}
			}
			else
			{
				// We have the type. Default and type/domain are exclusive for now.

				MetaName newDomainName;
				dyn_fld newDom;

				if (field->typeOfName.hasData())
				{
					// Case a1: Internal domain -> domain.
					// Case a2: Domain -> domain.

					newDomainName = field->typeOfName;

					if (fb_utils::implicit_domain(newDomainName.c_str()))
					{
						// msg 224: "Cannot use the internal domain %s as new type for field %s".
						status_exception::raise(
							Arg::PrivateDyn(224) << newDomainName << field->fld_name);
					}

					// Get the domain information.
					if (!METD_get_domain(dsqlScratch->getTransaction(), field, newDomainName))
					{
						// Specified domain or source field does not exist.
						status_exception::raise(
							Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
							Arg::Gds(isc_dsql_command_err) <<
							Arg::Gds(isc_dsql_domain_not_found) << newDomainName);
					}

					DDL_resolve_intl_type(dsqlScratch, field, NULL);

					// If the original definition was a base field type, remove the
					// entries from RDB$FIELDS.
					if (wasInternalDomain)
					{
						// Case a1: Internal domain -> domain.
						ERASE FLD;
					}
				}
				else
				{
					// Case b1: Internal domain -> internal domain.
					// Case b2: Domain -> internal domain.

					// If COMPUTED was specified but the type wasn't, we use the type of
					// the computed expression.
					if (clause->computed && field->dtype == dtype_unknown)
					{
						dsc desc;
						DsqlDescMaker::fromNode(dsqlScratch, &desc, clause->computed->value);

						field->dtype = desc.dsc_dtype;
						field->length = desc.dsc_length;
						field->scale = desc.dsc_scale;

						if (field->dtype <= dtype_any_text)
						{
							field->charSetId = DSC_GET_CHARSET(&desc);
							field->collationId = DSC_GET_COLLATE(&desc);
						}
						else
							field->subType = desc.dsc_sub_type;
					}

					field->resolve(dsqlScratch, true);

					if (wasInternalDomain)	// Case b1: Internal domain -> internal domain.
					{
						MODIFY FLD
							updateRdbFields(field,
								FLD.RDB$FIELD_TYPE,
								FLD.RDB$FIELD_LENGTH,
								FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
								FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
								FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
								FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
								FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
								FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
								FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);
						END_MODIFY

						newDom.dyn_fld_source = origDom.dyn_fld_source;
					}
					else	// Case b2: Domain -> internal domain.
						storeGlobalField(tdbb, transaction, newDomainName, field);
				}

				if (!clause->computed && !isView)
				{
					if (newDomainName.hasData())
						newDom.dyn_fld_source = newDomainName;

					AlterDomainNode::getDomainType(tdbb, transaction, newDom);
					AlterDomainNode::checkUpdate(origDom, newDom);

					if (!RFR.RDB$GENERATOR_NAME.NULL)
					{
						if (!newDom.dyn_dsc.isExact() || newDom.dyn_dsc.dsc_scale != 0)
						{
							// Identity column @1 of table @2 must be exact numeric with zero scale.
							status_exception::raise(Arg::PrivateDyn(273) << field->fld_name << name);
						}
					}
				}

				if (newDomainName.hasData())
				{
					MODIFY RFR USING
						RFR.RDB$FIELD_SOURCE.NULL = FALSE;
						strcpy(RFR.RDB$FIELD_SOURCE, newDomainName.c_str());

						if (clause->computed)
						{
							RFR.RDB$UPDATE_FLAG.NULL = FALSE;
							RFR.RDB$UPDATE_FLAG = 1;
						}

						RFR.RDB$COLLATION_ID.NULL = TRUE;	// CORE-2426
					END_MODIFY
				}
			}

			if (clause->computed)
			{
				// We can alter FLD directly here because if we are setting a computed expression,
				// it means the field already was computed. And if it was, it should be the
				// "b1 case", where the field source does not change.
				// This assumption may change, especially when this function starts dealing
				// with views.

				MODIFY FLD
					FLD.RDB$COMPUTED_SOURCE.NULL = FALSE;
					attachment->storeMetaDataBlob(tdbb, transaction, &FLD.RDB$COMPUTED_SOURCE,
						computedSource);

					FLD.RDB$COMPUTED_BLR.NULL = FALSE;
					attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR,
						computedValue);
				END_MODIFY
			}

			AutoRequest request2;

			FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				PRM IN RDB$PROCEDURE_PARAMETERS
				WITH PRM.RDB$RELATION_NAME = name.c_str() AND
					 PRM.RDB$FIELD_NAME = field->fld_name.c_str()
			{
				MODIFY PRM USING
					strcpy(PRM.RDB$FIELD_SOURCE, RFR.RDB$FIELD_SOURCE);
				END_MODIFY
			}
			END_FOR

			request2.reset();

			FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				ARG IN RDB$FUNCTION_ARGUMENTS
				WITH ARG.RDB$RELATION_NAME = name.c_str() AND
					 ARG.RDB$FIELD_NAME = field->fld_name.c_str()
			{
				MODIFY ARG USING
					strcpy(ARG.RDB$FIELD_SOURCE, RFR.RDB$FIELD_SOURCE);
				END_MODIFY
			}
			END_FOR

			request2.reset();

			FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				RFR2 IN RDB$RELATION_FIELDS CROSS
				VRL IN RDB$VIEW_RELATIONS
				WITH VRL.RDB$RELATION_NAME EQ name.c_str() AND
					 VRL.RDB$PACKAGE_NAME MISSING AND
					 VRL.RDB$CONTEXT_TYPE EQ VCT_TABLE AND
					 RFR2.RDB$RELATION_NAME EQ VRL.RDB$VIEW_NAME AND
					 RFR2.RDB$VIEW_CONTEXT EQ VRL.RDB$VIEW_CONTEXT AND
					 RFR2.RDB$BASE_FIELD = field->fld_name.c_str()
			{
				MODIFY RFR2
				{
					strcpy(RFR2.RDB$FIELD_SOURCE, RFR.RDB$FIELD_SOURCE);
				}
				END_MODIFY
			}
			END_FOR
		}
		END_FOR

		if (!found)
		{
			// msg 176: "column %s does not exist in table/view %s"
			status_exception::raise(Arg::PrivateDyn(176) << field->fld_name << name);
		}

		// Update any indices that exist.
		AlterDomainNode::modifyLocalFieldIndex(tdbb, transaction, name,
			field->fld_name, field->fld_name);
	}
	catch (const Exception&)
	{
		clearPermanentField(relation, permanent);
		throw;
	}

	clearPermanentField(relation, permanent);
}


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


// Delete a global field if it's not used in others objects.
void DropRelationNode::deleteGlobalField(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& globalName)
{
	AutoCacheRequest request(tdbb, drq_e_l_gfld, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FLD IN RDB$FIELDS
		WITH FLD.RDB$FIELD_NAME EQ globalName.c_str() AND
			 FLD.RDB$VALIDATION_SOURCE MISSING AND
			 FLD.RDB$NULL_FLAG MISSING AND
			 FLD.RDB$DEFAULT_SOURCE MISSING AND
			 FLD.RDB$FIELD_NAME STARTING WITH IMPLICIT_DOMAIN_PREFIX AND
			 (NOT ANY RFR IN RDB$RELATION_FIELDS WITH
				RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME) AND
			 (NOT ANY PRM IN RDB$PROCEDURE_PARAMETERS WITH
				PRM.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME) AND
			 (NOT ANY ARG IN RDB$FUNCTION_ARGUMENTS WITH
				ARG.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME)
	{
		DropDomainNode::deleteDimensionRecords(tdbb, transaction, globalName);
		ERASE FLD;
	}
	END_FOR
}

string DropRelationNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, view);
	NODE_PRINT(printer, silent);

	return "DropRelationNode";
}

void DropRelationNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	if (view)
		SCL_check_view(tdbb, &dscName, SCL_drop);
	else
		SCL_check_relation(tdbb, &dscName, SCL_drop);
}

void DropRelationNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	jrd_rel* rel_drop = MET_lookup_relation(tdbb, name);
	if (rel_drop)
		MET_scan_relation(tdbb, rel_drop);

	const dsql_rel* relation = METD_get_relation(transaction, dsqlScratch, name);

	if (!relation && silent)
		return;

	// Check that DROP TABLE is dropping a table and that DROP VIEW is dropping a view.
	if (view)
	{
		if (!relation || (relation && !(relation->rel_flags & REL_view)))
		{
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_dsql_view_not_found) << name);
		}
	}
	else
	{
		if (!relation || (relation && (relation->rel_flags & REL_view)))
		{
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_dsql_table_not_found) << name);
		}
	}

	const int ddlTriggerAction = (view ? DDL_TRIGGER_DROP_VIEW : DDL_TRIGGER_DROP_TABLE);

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_l_relation, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		R IN RDB$RELATIONS
		WITH R.RDB$RELATION_NAME EQ name.c_str()
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name, NULL);
		found = true;
	}
	END_FOR

	request.reset(tdbb, drq_e_rel_con2, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		CRT IN RDB$RELATION_CONSTRAINTS
		WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND
			 (CRT.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY OR
			  CRT.RDB$CONSTRAINT_TYPE EQ UNIQUE_CNSTRT OR
			  CRT.RDB$CONSTRAINT_TYPE EQ FOREIGN_KEY)
		SORTED BY ASCENDING CRT.RDB$CONSTRAINT_TYPE
	{
		ERASE CRT;
	}
	END_FOR

	request.reset(tdbb, drq_e_rel_idxs, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES
		WITH IDX.RDB$RELATION_NAME EQ name.c_str()
	{
		DropIndexNode::deleteSegmentRecords(tdbb, transaction, IDX.RDB$INDEX_NAME);
		ERASE IDX;
	}
	END_FOR

	request.reset(tdbb, drq_e_trg_msgs2, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		TM IN RDB$TRIGGER_MESSAGES
		CROSS T IN RDB$TRIGGERS
		WITH T.RDB$RELATION_NAME EQ name.c_str() AND
			 TM.RDB$TRIGGER_NAME EQ T.RDB$TRIGGER_NAME
	{
		ERASE TM;
	}
	END_FOR

	// CVC: Moved this block here to avoid SF Bug #1111570.
	request.reset(tdbb, drq_e_rel_con3, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		CRT IN RDB$RELATION_CONSTRAINTS
		WITH CRT.RDB$RELATION_NAME EQ name.c_str() AND
			 (CRT.RDB$CONSTRAINT_TYPE EQ CHECK_CNSTRT OR
			  CRT.RDB$CONSTRAINT_TYPE EQ NOT_NULL_CNSTRT)
	{
		ERASE CRT;
	}
	END_FOR

	request.reset(tdbb, drq_e_rel_flds, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RFR IN RDB$RELATION_FIELDS
		WITH RFR.RDB$RELATION_NAME EQ name.c_str()
	{
		if (!RFR.RDB$GENERATOR_NAME.NULL)
			DropSequenceNode::deleteIdentity(tdbb, transaction, RFR.RDB$GENERATOR_NAME);

		ERASE RFR;

		if (!RFR.RDB$SECURITY_CLASS.NULL &&
			!strncmp(RFR.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN))
		{
			deleteSecurityClass(tdbb, transaction, RFR.RDB$SECURITY_CLASS);
		}

		deleteGlobalField(tdbb, transaction, RFR.RDB$FIELD_SOURCE);
	}
	END_FOR

	request.reset(tdbb, drq_e_view_rels, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		VR IN RDB$VIEW_RELATIONS
		WITH VR.RDB$VIEW_NAME EQ name.c_str()
	{
		ERASE VR;
	}
	END_FOR

	request.reset(tdbb, drq_e_relation, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		R IN RDB$RELATIONS
		WITH R.RDB$RELATION_NAME EQ name.c_str()
	{
		ERASE R;

		if (!R.RDB$SECURITY_CLASS.NULL &&
			!strncmp(R.RDB$SECURITY_CLASS, SQL_SECCLASS_PREFIX, SQL_SECCLASS_PREFIX_LEN))
		{
			deleteSecurityClass(tdbb, transaction, R.RDB$SECURITY_CLASS);
		}
	}
	END_FOR

	if (!found)
	{
		// msg 61: "Relation not found"
		status_exception::raise(Arg::PrivateDyn(61));
	}

	// Triggers must be deleted after check constraints

	MetaName triggerName;

	request.reset(tdbb, drq_e_trigger2, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$TRIGGERS
		WITH X.RDB$RELATION_NAME EQ name.c_str()
	{
		triggerName = X.RDB$TRIGGER_NAME;
		ERASE X;

		AutoCacheRequest request2(tdbb, drq_e_trg_prv, DYN_REQUESTS);

		FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			PRIV IN RDB$USER_PRIVILEGES
			WITH PRIV.RDB$USER EQ triggerName.c_str() AND
				PRIV.RDB$USER_TYPE = obj_trigger AND
				PRIV.RDB$GRANTOR NOT MISSING
		{
			ERASE PRIV;
		}
		END_FOR
	}
	END_FOR

	request.reset(tdbb, drq_e_usr_prvs, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$RELATION_NAME EQ name.c_str() AND
			 PRIV.RDB$OBJECT_TYPE = obj_relation AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	request.reset(tdbb, drq_e_view_prv, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH PRIV.RDB$USER EQ name.c_str() AND
			 PRIV.RDB$USER_TYPE = obj_view AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

	// Drop table from all publications

	request.reset(tdbb, drq_e_pub_tab_all, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PTAB IN RDB$PUBLICATION_TABLES
		WITH PTAB.RDB$TABLE_NAME EQ name.c_str()
	{
		ERASE PTAB;
	}
	END_FOR

	if (found)
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name, NULL);
	else
	{
		// msg 61: "Relation not found"
		status_exception::raise(Arg::PrivateDyn(61));
	}

	savePoint.release();	// everything is ok

	METD_drop_relation(transaction, name.c_str());
	MET_dsql_cache_release(tdbb, SYM_relation, name);
}


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


string CreateAlterViewNode::internalPrint(NodePrinter& printer) const
{
	RelationNode::internalPrint(printer);

	NODE_PRINT(printer, create);
	NODE_PRINT(printer, alter);
	NODE_PRINT(printer, viewFields);
	NODE_PRINT(printer, selectExpr);
	NODE_PRINT(printer, source);
	NODE_PRINT(printer, withCheckOption);

	return "CreateAlterViewNode";
}

DdlNode* CreateAlterViewNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	source.ltrim("\n\r\t ");
	return DdlNode::dsqlPass(dsqlScratch);
}

void CreateAlterViewNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	if (alter)
	{
		if (SCL_check_view(tdbb, &dscName, SCL_alter) || !create)
			return;
	}

	SCL_check_create_access(tdbb, SCL_object_view);
}

void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;
	const MetaString& ownerName = attachment->getEffectiveUserName();

	const dsql_rel* modifyingView = NULL;

	if (alter)
	{
		modifyingView = METD_get_relation(dsqlScratch->getTransaction(), dsqlScratch, name);

		if (!modifyingView && !create)
			status_exception::raise(Arg::Gds(isc_dyn_view_not_found) << name);
	}

	saveRelation(tdbb, dsqlScratch, name, true, modifyingView == NULL);

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	const int ddlTriggerAction = (modifyingView ? DDL_TRIGGER_ALTER_VIEW : DDL_TRIGGER_CREATE_VIEW);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name, NULL);

	if (!modifyingView)
		DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_relation);

	// Compile the SELECT statement into a record selection expression, making sure to bump the
	// context number since view contexts start at 1 (except for computed fields) -- note that
	// calling PASS1_rse directly rather than PASS1_statement saves the context stack.

	dsqlScratch->resetContextStack();
	++dsqlScratch->contextNumber;
	RseNode* rse = PASS1_rse(dsqlScratch, selectExpr, false);

	dsqlScratch->getBlrData().clear();
	dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

	GEN_expr(dsqlScratch, rse);
	dsqlScratch->appendUChar(blr_eoc);

	// Store the blr and source string for the view definition.

	if (modifyingView)
	{
		AutoCacheRequest request(tdbb, drq_m_view, DYN_REQUESTS);
		bool found = false;

		FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			REL IN RDB$RELATIONS
			WITH REL.RDB$RELATION_NAME EQ name.c_str() AND
				 REL.RDB$VIEW_BLR NOT MISSING
		{
			found = true;

			MODIFY REL
				attachment->storeMetaDataBlob(tdbb, transaction, &REL.RDB$VIEW_SOURCE, source);
				attachment->storeBinaryBlob(tdbb, transaction, &REL.RDB$VIEW_BLR,
					dsqlScratch->getBlrData());
			END_MODIFY
		}
		END_FOR

		if (!found)
			status_exception::raise(Arg::Gds(isc_dyn_view_not_found) << name);

		AutoRequest request2;

		FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			VR IN RDB$VIEW_RELATIONS
			WITH VR.RDB$VIEW_NAME EQ name.c_str()
		{
			ERASE VR;
		}
		END_FOR

		request2.reset();

		FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			TRG IN RDB$TRIGGERS
			WITH TRG.RDB$RELATION_NAME EQ name.c_str() AND
				 TRG.RDB$SYSTEM_FLAG EQ fb_sysflag_view_check
		{
			ERASE TRG;
		}
		END_FOR
	}
	else
	{
		AutoCacheRequest request(tdbb, drq_s_rels, DYN_REQUESTS);

		STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			REL IN RDB$RELATIONS
		{
			strcpy(REL.RDB$RELATION_NAME, name.c_str());
			REL.RDB$SYSTEM_FLAG = 0;
			REL.RDB$FLAGS = REL_sql;
			REL.RDB$RELATION_TYPE = SSHORT(rel_view);

			attachment->storeMetaDataBlob(tdbb, transaction, &REL.RDB$VIEW_SOURCE, source);
			attachment->storeBinaryBlob(tdbb, transaction, &REL.RDB$VIEW_BLR, dsqlScratch->getBlrData());
		}
		END_STORE

		storePrivileges(tdbb, transaction, name, obj_relation, ALL_PRIVILEGES);
	}

	// Define the view source relations from the statement contexts and union contexts.

	while (dsqlScratch->derivedContext.hasData())
		dsqlScratch->context->push(dsqlScratch->derivedContext.pop());

	while (dsqlScratch->unionContext.hasData())
		dsqlScratch->context->push(dsqlScratch->unionContext.pop());

	AutoCacheRequest request(tdbb, drq_s_view_rels, DYN_REQUESTS);

	for (DsqlContextStack::iterator temp(*dsqlScratch->context); temp.hasData(); ++temp)
	{
		const dsql_ctx* context = temp.object();
		const dsql_rel* relation = context->ctx_relation;
		const dsql_prc* procedure = context->ctx_procedure;

		if (relation || procedure)
		{
			const MetaName& refName = relation ? relation->rel_name : procedure->prc_name.identifier;
			const char* contextName = context->ctx_alias.hasData() ?
				context->ctx_alias.c_str() : refName.c_str();

			ViewContextType ctxType;
			if (relation)
			{
				if (!(relation->rel_flags & REL_view))
					ctxType = VCT_TABLE;
				else
					ctxType = VCT_VIEW;
			}
			else //if (procedure)
				ctxType = VCT_PROCEDURE;

			STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				VRL IN RDB$VIEW_RELATIONS
			{
				strcpy(VRL.RDB$VIEW_NAME, name.c_str());
				strcpy(VRL.RDB$RELATION_NAME, refName.c_str());
				VRL.RDB$CONTEXT_TYPE = SSHORT(ctxType);
				VRL.RDB$VIEW_CONTEXT = context->ctx_context;
				strcpy(VRL.RDB$CONTEXT_NAME, contextName);

				if (procedure && procedure->prc_name.package.hasData())
				{
					VRL.RDB$PACKAGE_NAME.NULL = FALSE;
					strcpy(VRL.RDB$PACKAGE_NAME, procedure->prc_name.package.c_str());
				}
				else
					VRL.RDB$PACKAGE_NAME.NULL = TRUE;
			}
			END_STORE
		}
	}

	// Check privileges on base tables and views.

	request.reset(tdbb, drq_l_view_rels, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		VRL IN RDB$VIEW_RELATIONS CROSS
		PREL IN RDB$RELATIONS OVER RDB$RELATION_NAME
		WITH VRL.RDB$PACKAGE_NAME MISSING AND
			 VRL.RDB$VIEW_NAME EQ name.c_str()
	{
		// CVC: This never matches so it causes unnecessary calls to verify,
		// so I included a call to strip trailing blanks.
		fb_utils::exact_name_limit(PREL.RDB$OWNER_NAME, sizeof(PREL.RDB$OWNER_NAME));

		if (ownerName != PREL.RDB$OWNER_NAME)
		{
			SecurityClass::flags_t priv;

			// I think this should be the responsability of DFW or the user will find ways to
			// circumvent DYN.
			priv = SCL_get_mask(tdbb, PREL.RDB$RELATION_NAME, "");

			if (!(priv & SCL_select))
			{
				// msg 32: no permission for %s access to %s %s
				status_exception::raise(
					Arg::Gds(isc_no_priv) << Arg::Str("SELECT") <<	//	Non-Translatable
					// Remember, a view may be based on a view.
					"TABLE/VIEW" <<	//  Non-Translatable
					// We want to print the name of the base table or view.
					MetaName(PREL.RDB$RELATION_NAME));
			}
		}
	}
	END_FOR

	// If there are field names defined for the view, match them in order with the items from the
	// SELECT. Otherwise use all the fields from the rse node that was created from the select
	// expression.

	const NestConst<ValueExprNode>* ptr = NULL;
	const NestConst<ValueExprNode>* end = NULL;

	if (viewFields)
	{
		ptr = viewFields->items.begin();
		end = viewFields->items.end();
	}

	// Go through the fields list, defining or modifying the local fields;
	// If an expression is specified rather than a field, define a global
	// field for the computed value as well.

	ValueListNode* items = rse->dsqlSelectList;
	NestConst<ValueExprNode>* itemsPtr = items->items.begin();
	SortedArray<dsql_fld*> modifiedFields;
	bool updatable = true;
	SSHORT position = 0;

	for (NestConst<ValueExprNode>* itemsEnd = items->items.end();
		 itemsPtr < itemsEnd; ++itemsPtr, ++position)
	{
		ValueExprNode* fieldNode = *itemsPtr;

		// Determine the proper field name, replacing the default if necessary.

		ValueExprNode* nameNode = fieldNode;
		const char* aliasName = NULL;

		while (nodeIs<DsqlAliasNode>(nameNode) || nodeIs<DerivedFieldNode>(nameNode) || nodeIs<DsqlMapNode>(nameNode))
		{
			DsqlAliasNode* aliasNode;
			DsqlMapNode* mapNode;
			DerivedFieldNode* derivedField;

			if ((aliasNode = nodeAs<DsqlAliasNode>(nameNode)))
			{
				if (!aliasName)
					aliasName = aliasNode->name.c_str();
				nameNode = aliasNode->value;
			}
			else if ((mapNode = nodeAs<DsqlMapNode>(nameNode)))
				nameNode = mapNode->map->map_node;
			else if ((derivedField = nodeAs<DerivedFieldNode>(nameNode)))
			{
				if (!aliasName)
					aliasName = derivedField->name.c_str();
				nameNode = derivedField->value;
			}
		}

		const dsql_fld* nameField = NULL;
		const FieldNode* fieldNameNode = nodeAs<FieldNode>(nameNode);

		if (fieldNameNode)
			nameField = fieldNameNode->dsqlField;

		const TEXT* fieldStr = NULL;

		if (aliasName)
			fieldStr = aliasName;
		else if (nameField)
			fieldStr = nameField->fld_name.c_str();

		// Check if this is a field or an expression.

		DsqlAliasNode* aliasNode = nodeAs<DsqlAliasNode>(fieldNode);

		if (aliasNode)
			fieldNode = aliasNode->value;

		dsql_fld* field = NULL;
		const dsql_ctx* context = NULL;

		fieldNameNode = nodeAs<FieldNode>(fieldNode);

		if (fieldNameNode)
		{
			field = fieldNameNode->dsqlField;
			context = fieldNameNode->dsqlContext;
		}
		else
			updatable = false;

		// If this is an expression, check to make sure there is a name specified.

		if (!ptr && !fieldStr)
		{
			// must specify field name for view select expression
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_specify_field_err));
		}

		// CVC: Small modification here to catch any mismatch between number of
		// explicit field names in a view and number of fields in the select expression,
		// see comment below. This closes Firebird Bug #223059.
		if (ptr)
		{
			if (ptr < end)
				fieldStr = nodeAs<FieldNode>(*ptr)->dsqlName.c_str();
			else
			{
				// Generate an error when going out of this loop.
				++ptr;
				break;
			}

			++ptr;
		}

		// If not an expression, point to the proper base relation field,
		// else make up an SQL field with generated global field for calculations.

		dsql_fld* relField = NULL;

		if (modifyingView)	// if we're modifying a view
		{
			for (relField = modifyingView->rel_fields; relField; relField = relField->fld_next)
			{
				if (relField->fld_name == fieldStr)
				{
					if (modifiedFields.exist(relField))
					{
						// column @1 appears more than once in ALTER VIEW
						status_exception::raise(
							Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
							Arg::Gds(isc_dsql_command_err) <<
							Arg::Gds(isc_dsql_col_more_than_once_view) << Arg::Str(fieldStr));
					}

					modifiedFields.add(relField);
					break;
				}
			}
		}

		FieldDefinition fieldDefinition(*tdbb->getDefaultPool());
		fieldDefinition.relationName = name;
		fieldDefinition.name = fieldStr;
		fieldDefinition.position = position;

		// CVC: Not sure if something should be done now that isc_dyn_view_context is used here,
		// but if alter view is going to work, maybe we need here the context type and package, too.
		if (field)
		{
			field->resolve(dsqlScratch);

			fieldDefinition.viewContext = context->ctx_context;
			fieldDefinition.baseField = field->fld_name;

			if (field->dtype <= dtype_any_text)
				fieldDefinition.collationId = field->collationId;

			if (relField)	// modifying a view
			{
				// We're now modifying a field and it will be based on another one. So if the old
				// field was an expression, delete it now.

				AutoRequest request2;

				FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
					RFL IN RDB$RELATION_FIELDS CROSS
					FLD IN RDB$FIELDS
					WITH RFL.RDB$FIELD_NAME EQ fieldStr AND
						 RFL.RDB$RELATION_NAME EQ name.c_str() AND
						 RFL.RDB$BASE_FIELD MISSING AND
						 FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE
				{
					bool wasInternalDomain = fb_utils::implicit_domain(FLD.RDB$FIELD_NAME);
					fb_assert(wasInternalDomain);

					if (wasInternalDomain)
						ERASE FLD;
				}
				END_FOR

				fieldDefinition.modify(tdbb, transaction);
			}
			else
				fieldDefinition.store(tdbb, transaction);
		}
		else
		{
			dsqlScratch->getBlrData().clear();
			dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);
			GEN_expr(dsqlScratch, fieldNode);
			dsqlScratch->appendUChar(blr_eoc);

			// Get the type of the expression.
			dsc desc;
			DsqlDescMaker::fromNode(dsqlScratch, &desc, fieldNode);

			dsql_fld newField(*tdbb->getDefaultPool());
			newField.dtype = desc.dsc_dtype;
			newField.length = desc.dsc_length;
			newField.scale = desc.dsc_scale;

			if (desc.isText() || (desc.isBlob() && desc.getBlobSubType() == isc_blob_text))
			{
				newField.charSetId = desc.getCharSet();
				newField.collationId = desc.getCollation();
			}

			if (desc.isText())
			{
				const USHORT adjust =
					(desc.dsc_dtype == dtype_varying) ? sizeof(USHORT) : 0;
				const USHORT bpc =
					METD_get_charset_bpc(dsqlScratch->getTransaction(), newField.charSetId.value);

				newField.charLength = (newField.length - adjust) / bpc;
			}
			else
				newField.subType = desc.dsc_sub_type;

			newField.setExactPrecision();

			if (relField)	// modifying a view
			{
				AutoRequest request2;

				FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
					RFL IN RDB$RELATION_FIELDS CROSS
					FLD IN RDB$FIELDS
					WITH RFL.RDB$FIELD_NAME EQ fieldStr AND
						 RFL.RDB$RELATION_NAME EQ name.c_str() AND
						 RFL.RDB$BASE_FIELD MISSING AND
						 FLD.RDB$FIELD_NAME EQ RFL.RDB$FIELD_SOURCE
				{
					bool wasInternalDomain = fb_utils::implicit_domain(FLD.RDB$FIELD_NAME);
					fb_assert(wasInternalDomain);

					if (wasInternalDomain)
					{
						fieldDefinition.fieldSource = FLD.RDB$FIELD_NAME;

						MODIFY FLD
							updateRdbFields(&newField,
								FLD.RDB$FIELD_TYPE,
								FLD.RDB$FIELD_LENGTH,
								FLD.RDB$FIELD_SUB_TYPE.NULL, FLD.RDB$FIELD_SUB_TYPE,
								FLD.RDB$FIELD_SCALE.NULL, FLD.RDB$FIELD_SCALE,
								FLD.RDB$CHARACTER_SET_ID.NULL, FLD.RDB$CHARACTER_SET_ID,
								FLD.RDB$CHARACTER_LENGTH.NULL, FLD.RDB$CHARACTER_LENGTH,
								FLD.RDB$FIELD_PRECISION.NULL, FLD.RDB$FIELD_PRECISION,
								FLD.RDB$COLLATION_ID.NULL, FLD.RDB$COLLATION_ID,
								FLD.RDB$SEGMENT_LENGTH.NULL, FLD.RDB$SEGMENT_LENGTH);

							FLD.RDB$COMPUTED_BLR.NULL = FALSE;
							attachment->storeBinaryBlob(tdbb, transaction, &FLD.RDB$COMPUTED_BLR,
								dsqlScratch->getBlrData());
						END_MODIFY
					}
				}
				END_FOR

				if (fieldDefinition.fieldSource.isEmpty())
				{
					storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, &newField,
						"", dsqlScratch->getBlrData());
				}

				fieldDefinition.modify(tdbb, transaction);
			}
			else
			{
				storeGlobalField(tdbb, transaction, fieldDefinition.fieldSource, &newField,
					"", dsqlScratch->getBlrData());

				fieldDefinition.store(tdbb, transaction);
			}
		}

		if (fieldStr)
			saveField(tdbb, dsqlScratch, fieldStr);
	}

	// CVC: This message was not catching the case when
	// #fields < items in select list, see comment above.

	if (ptr != end)
	{
		// number of fields does not match select list
		status_exception::raise(
			Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
			Arg::Gds(isc_dsql_command_err) <<
			Arg::Gds(isc_num_field_err));
	}

	if (modifyingView)	// modifying a view
	{
		// Delete the old fields not present in the new definition.
		for (dsql_fld* relField = modifyingView->rel_fields; relField; relField = relField->fld_next)
		{
			if (!modifiedFields.exist(relField))
				deleteLocalField(tdbb, transaction, name, relField->fld_name);
		}
	}

	// Setup to define triggers for WITH CHECK OPTION.

	if (withCheckOption)
	{
		if (!updatable)
		{
			// Only simple column names permitted for VIEW WITH CHECK OPTION
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_col_name_err));
		}

		RseNode* querySpec = nodeAs<RseNode>(selectExpr->querySpec);
		fb_assert(querySpec);

		if (querySpec->dsqlFrom->items.getCount() != 1 ||
			!nodeIs<ProcedureSourceNode>(querySpec->dsqlFrom->items[0]))
		{
			// Only one table allowed for VIEW WITH CHECK OPTION
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_table_view_err));
		}

		if (!querySpec->dsqlWhere)
		{
			// No where clause for VIEW WITH CHECK OPTION
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_where_err));
		}

		if (querySpec->dsqlDistinct || querySpec->dsqlGroup || querySpec->dsqlHaving)
		{
			// DISTINCT, GROUP or HAVING not permitted for VIEW WITH CHECK OPTION
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_distinct_err));
		}

		dsqlScratch->flags |= DsqlCompilerScratch::FLAG_VIEW_WITH_CHECK;

		createCheckTrigger(tdbb, dsqlScratch, items, PRE_MODIFY_TRIGGER);
		createCheckTrigger(tdbb, dsqlScratch, items, PRE_STORE_TRIGGER);
	}

	dsqlScratch->resetContextStack();

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name, NULL);

	savePoint.release();	// everything is ok

	// Update DSQL cache
	METD_drop_relation(transaction, name);
	MET_dsql_cache_release(tdbb, SYM_relation, name);
}

// Generate a trigger to implement the WITH CHECK OPTION clause for a VIEW.
void CreateAlterViewNode::createCheckTrigger(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	ValueListNode* items, TriggerType triggerType)
{
	MemoryPool& pool = *tdbb->getDefaultPool();

	// Specify that the trigger should abort if the condition is not met.
	ExceptionNode* exceptionNode = FB_NEW_POOL(pool) ExceptionNode(pool, CHECK_CONSTRAINT_EXCEPTION);
	exceptionNode->exception->type = ExceptionItem::GDS_CODE;

	AutoSetRestore<bool> autoCheckConstraintTrigger(&dsqlScratch->checkConstraintTrigger, true);

	RelationSourceNode* relationNode = dsqlNode;

	// Generate the trigger blr.

	dsqlScratch->getBlrData().clear();
	dsqlScratch->getDebugData().clear();
	dsqlScratch->appendUChar(dsqlScratch->isVersion4() ? blr_version4 : blr_version5);

	dsqlScratch->appendUChar(blr_begin);

	dsqlScratch->resetContextStack();

	RseNode* querySpec = nodeAs<RseNode>(selectExpr->querySpec);
	fb_assert(querySpec);

	ProcedureSourceNode* sourceNode = nodeAs<ProcedureSourceNode>(querySpec->dsqlFrom->items[0]);

	if (triggerType == PRE_MODIFY_TRIGGER)
	{
		dsqlScratch->contextNumber = 2;

		RelationSourceNode* baseRelation = FB_NEW_POOL(pool) RelationSourceNode(pool,
			sourceNode->dsqlName.identifier);
		baseRelation->alias = sourceNode->alias;

		dsqlScratch->appendUChar(blr_for);

		RseNode* rse = FB_NEW_POOL(pool) RseNode(pool);
		rse->dsqlStreams = FB_NEW_POOL(pool) RecSourceListNode(pool, 1);

		rse->dsqlStreams->items[0] = baseRelation;
		rse->dsqlStreams->items[0] = doDsqlPass(dsqlScratch, rse->dsqlStreams->items[0]);
		rse->dsqlWhere = doDsqlPass(dsqlScratch, querySpec->dsqlWhere);

		dsqlScratch->contextNumber = OLD_CONTEXT_VALUE;

		dsql_ctx* oldContext;

		{	/// scope
			AutoSetRestore<string> autoAlias(&relationNode->alias, sourceNode->alias);
			relationNode->alias = OLD_CONTEXT_NAME;

			oldContext = PASS1_make_context(dsqlScratch, relationNode);
			oldContext->ctx_flags |= CTX_system;
		}

		// Get the list of values and fields to compare to -- if there is no list of fields, get all
		// fields in the base relation that are not computed.

		ValueListNode* valuesNode = viewFields;
		ValueListNode* fieldsNode = querySpec->dsqlSelectList;

		if (!fieldsNode)
		{
			const dsql_rel* relation = METD_get_relation(dsqlScratch->getTransaction(),
				dsqlScratch, name);
			fieldsNode = FB_NEW_POOL(pool) ValueListNode(pool, 0u);

			for (const dsql_fld* field = relation->rel_fields; field; field = field->fld_next)
			{
				if (!(field->flags & FLD_computed))
					fieldsNode->add(MAKE_field_name(field->fld_name.c_str()));
			}
		}

		if (!valuesNode)
			valuesNode = fieldsNode;

		// Generate the list of assignments to fields in the base relation.

		NestConst<ValueExprNode>* ptr = fieldsNode->items.begin();
		const NestConst<ValueExprNode>* const end = fieldsNode->items.end();
		NestConst<ValueExprNode>* ptr2 = valuesNode->items.begin();
		const NestConst<ValueExprNode>* const end2 = valuesNode->items.end();
		int andArg = 0;

		BinaryBoolNode* andNode = FB_NEW_POOL(pool) BinaryBoolNode(pool, blr_and);

		for (; ptr != end && ptr2 != end2; ++ptr, ++ptr2)
		{
			NestConst<ValueExprNode> fieldNod = *ptr;
			NestConst<ValueExprNode> valueNod = *ptr2;
			DsqlAliasNode* aliasNode;

			if ((aliasNode = nodeAs<DsqlAliasNode>(fieldNod)))
				fieldNod = aliasNode->value;

			if ((aliasNode = nodeAs<DsqlAliasNode>(valueNod)))
				valueNod = aliasNode->value;

			FieldNode* fieldNode = nodeAs<FieldNode>(fieldNod);
			FieldNode* valueNode = nodeAs<FieldNode>(valueNod);

			// Generate the actual comparisons.

			if (fieldNode && valueNode)
			{
				FieldNode* oldValueNode = FB_NEW_POOL(pool) FieldNode(pool);
				oldValueNode->dsqlName = (aliasNode ? aliasNode->name : valueNode->dsqlName);
				oldValueNode->dsqlQualifier = OLD_CONTEXT_NAME;

				valueNod = oldValueNode->dsqlPass(dsqlScratch);
				fieldNod = fieldNode->dsqlPass(dsqlScratch);

				BoolExprNode* equivNode = FB_NEW_POOL(pool) ComparativeBoolNode(pool, blr_equiv,
					valueNod, fieldNod);

				rse->dsqlWhere = PASS1_compose(rse->dsqlWhere, equivNode, blr_and);
			}
		}

		GEN_expr(dsqlScratch, rse);
	}

	// ASF: We'll now map the view's base table into the trigger's NEW context.

	++dsqlScratch->scopeLevel;
	dsqlScratch->contextNumber = NEW_CONTEXT_VALUE;

	dsql_ctx* newContext;

	{	/// scope
		AutoSetRestore<string> autoAlias(&relationNode->alias, sourceNode->alias);

		if (relationNode->alias.isEmpty())
			relationNode->alias = sourceNode->dsqlName.identifier.c_str();

		newContext = PASS1_make_context(dsqlScratch, relationNode);
		newContext->ctx_flags |= CTX_system;

		if (triggerType == PRE_STORE_TRIGGER)
			newContext->ctx_flags |= CTX_view_with_check_store;
		else
			newContext->ctx_flags |= CTX_view_with_check_modify;
	}

	// Replace the view's field names by the base table field names. Save the original names
	// to restore after the condition processing.

	dsql_fld* field = newContext->ctx_relation->rel_fields;
	ObjectsArray<MetaName> savedNames;

	// ASF: rel_fields entries are in reverse order.
	for (NestConst<ValueExprNode>* ptr = items->items.end();
		 ptr-- != items->items.begin();
		 field = field->fld_next)
	{
		ValueExprNode* valueNode = *ptr;
		DsqlAliasNode* aliasNode;

		if ((aliasNode = nodeAs<DsqlAliasNode>(valueNode)))
			valueNode = aliasNode->value;

		FieldNode* fieldNode = nodeAs<FieldNode>(valueNode);
		fb_assert(fieldNode);

		savedNames.add(field->fld_name);

		dsql_fld* queryField = fieldNode->dsqlField;

		field->fld_name = queryField->fld_name;
		field->dtype = queryField->dtype;
		field->scale = queryField->scale;
		field->subType = queryField->subType;
		field->length = queryField->length;
		field->flags = queryField->flags;
		field->charSetId = queryField->charSetId;
		field->collationId = queryField->collationId;
	}

	dsqlScratch->appendUChar(blr_if);

	// Process the condition for firing the trigger.
	NestConst<BoolExprNode> condition = doDsqlPass(dsqlScratch, querySpec->dsqlWhere);

	// Restore the field names. This must happen before the condition's BLR generation.

	field = newContext->ctx_relation->rel_fields;

	for (ObjectsArray<MetaName>::iterator i = savedNames.begin(); i != savedNames.end(); ++i)
	{
		field->fld_name = *i;
		field = field->fld_next;
	}

	GEN_expr(dsqlScratch, condition);
	dsqlScratch->appendUChar(blr_begin);
	dsqlScratch->appendUChar(blr_end);

	// Generate the action statement for the trigger.
	exceptionNode->dsqlPass(dsqlScratch)->genBlr(dsqlScratch);

	dsqlScratch->appendUChar(blr_end);	// of begin
	dsqlScratch->appendUChar(blr_eoc);

	dsqlScratch->resetContextStack();

	TriggerDefinition trigger(pool);
	trigger.systemFlag = fb_sysflag_view_check;
	trigger.relationName = name;
	trigger.type = triggerType;
	trigger.blrData = dsqlScratch->getBlrData();
	trigger.store(tdbb, dsqlScratch, dsqlScratch->getTransaction());
}


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


// Store an index.
void CreateIndexNode::store(thread_db* tdbb, jrd_tra* transaction, MetaName& name,
	const Definition& definition, MetaName* referredIndexName)
{
	if (name.isEmpty())
		DYN_UTIL_generate_index_name(tdbb, transaction, name, definition.type);

	DYN_UTIL_check_unique_name(tdbb, transaction, name, obj_index);

	AutoCacheRequest request(tdbb, drq_s_indices, DYN_REQUESTS);

	ULONG keyLength = 0;

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES
	{
		IDX.RDB$UNIQUE_FLAG.NULL = TRUE;
		IDX.RDB$INDEX_INACTIVE.NULL = TRUE;
		IDX.RDB$INDEX_TYPE.NULL = TRUE;
		IDX.RDB$FOREIGN_KEY.NULL = TRUE;
		IDX.RDB$EXPRESSION_SOURCE.NULL = TRUE;
		IDX.RDB$EXPRESSION_BLR.NULL = TRUE;
		strcpy(IDX.RDB$INDEX_NAME, name.c_str());
		strcpy(IDX.RDB$RELATION_NAME, definition.relation.c_str());
		IDX.RDB$RELATION_NAME.NULL = FALSE;
		IDX.RDB$SYSTEM_FLAG = 0;
		IDX.RDB$SYSTEM_FLAG.NULL = FALSE; // Probably redundant.

		// Check if the table is actually a view.

		AutoCacheRequest request2(tdbb, drq_l_view_idx, DYN_REQUESTS);

		FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			VREL IN RDB$RELATIONS
			WITH VREL.RDB$RELATION_NAME EQ IDX.RDB$RELATION_NAME
		{
			if (!VREL.RDB$VIEW_BLR.NULL)
			{
				// msg 181: "attempt to index a view"
				status_exception::raise(Arg::PrivateDyn(181));
			}
		}
		END_FOR

		if (definition.unique.specified)
		{
			IDX.RDB$UNIQUE_FLAG.NULL = FALSE;
			IDX.RDB$UNIQUE_FLAG = SSHORT(definition.unique.value);
		}

		if (definition.inactive.specified)
		{
			IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
			IDX.RDB$INDEX_INACTIVE = SSHORT(definition.inactive.value);
		}

		if (definition.descending.specified)
		{
			IDX.RDB$INDEX_TYPE.NULL = FALSE;
			IDX.RDB$INDEX_TYPE = SSHORT(definition.descending.value);
		}

		request2.reset(tdbb, drq_l_lfield, DYN_REQUESTS);

		for (FB_SIZE_T i = 0; i < definition.columns.getCount(); ++i)
		{
			for (FB_SIZE_T j = 0; j < i; ++j)
			{
				if (definition.columns[i] == definition.columns[j])
				{
					// msg 240 "Field %s cannot be used twice in index %s"
					status_exception::raise(
						Arg::PrivateDyn(240) << definition.columns[i] << IDX.RDB$INDEX_NAME);
				}
			}

			bool found = false;

			FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				F IN RDB$RELATION_FIELDS CROSS
				GF IN RDB$FIELDS
				WITH GF.RDB$FIELD_NAME EQ F.RDB$FIELD_SOURCE AND
					 F.RDB$FIELD_NAME EQ definition.columns[i].c_str() AND
					 IDX.RDB$RELATION_NAME EQ F.RDB$RELATION_NAME
			{
				ULONG length = 0;

				if (GF.RDB$FIELD_TYPE == blr_blob)
				{
					// msg 116 "attempt to index blob field in index %s"
					status_exception::raise(Arg::PrivateDyn(116) << IDX.RDB$INDEX_NAME);
				}
				else if (!GF.RDB$DIMENSIONS.NULL)
				{
					// msg 117 "attempt to index array field in index %s"
					status_exception::raise(Arg::PrivateDyn(117) << IDX.RDB$INDEX_NAME);
				}
				else if (!GF.RDB$COMPUTED_BLR.NULL)
				{
					// msg 179 "attempt to index COMPUTED BY field in index %s"
					status_exception::raise(Arg::PrivateDyn(179) << IDX.RDB$INDEX_NAME);
				}
				else if (GF.RDB$FIELD_TYPE == blr_varying || GF.RDB$FIELD_TYPE == blr_text)
				{
					// Compute the length of the key segment allowing for international
					// information. Note that we we must convert a <character set, collation>
					// type to an index type in order to compute the length.
					if (!F.RDB$COLLATION_ID.NULL)
					{
						length = INTL_key_length(tdbb,
							INTL_TEXT_TO_INDEX(INTL_CS_COLL_TO_TTYPE(
								GF.RDB$CHARACTER_SET_ID, F.RDB$COLLATION_ID)),
							GF.RDB$FIELD_LENGTH);
					}
					else if (!GF.RDB$COLLATION_ID.NULL)
					{
						length = INTL_key_length(tdbb,
							INTL_TEXT_TO_INDEX(INTL_CS_COLL_TO_TTYPE(
								GF.RDB$CHARACTER_SET_ID, GF.RDB$COLLATION_ID)),
							GF.RDB$FIELD_LENGTH);
					}
					else
						length = GF.RDB$FIELD_LENGTH;
				}
				else
					length = sizeof(double);

				if (keyLength)
				{
					keyLength += ((length + Ods::STUFF_COUNT - 1) / (unsigned) Ods::STUFF_COUNT) *
						(Ods::STUFF_COUNT + 1);
				}
				else
					keyLength = length;

				found = true;
			}
			END_FOR

			if (!found)
			{
				// msg 120 "Unknown columns in index %s"
				status_exception::raise(Arg::PrivateDyn(120) << IDX.RDB$INDEX_NAME);
			}
		}

		if (!definition.expressionBlr.isEmpty())
		{
			IDX.RDB$EXPRESSION_BLR.NULL = FALSE;
			IDX.RDB$EXPRESSION_BLR = definition.expressionBlr;
		}

		if (!definition.expressionSource.isEmpty())
		{
			IDX.RDB$EXPRESSION_SOURCE.NULL = FALSE;
			IDX.RDB$EXPRESSION_SOURCE = definition.expressionSource;
		}

		keyLength = ROUNDUP(keyLength, sizeof(SLONG));
		if (keyLength >= MAX_KEY)
		{
			// msg 118 "key size too big for index %s"
			status_exception::raise(Arg::PrivateDyn(118) << IDX.RDB$INDEX_NAME);
		}

		if (definition.columns.hasData())
		{
			request2.reset(tdbb, drq_s_idx_segs, DYN_REQUESTS);

			SSHORT position = 0;

			for (ObjectsArray<MetaName>::const_iterator segment(definition.columns.begin());
				 segment != definition.columns.end();
				 ++segment)
			{
				STORE(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
					X IN RDB$INDEX_SEGMENTS
				{
					strcpy(X.RDB$INDEX_NAME, IDX.RDB$INDEX_NAME);
					strcpy(X.RDB$FIELD_NAME, segment->c_str());
					X.RDB$FIELD_POSITION = position++;
				}
				END_STORE
			}
		}
		else if (IDX.RDB$EXPRESSION_BLR.NULL)
		{
			// msg 119 "no keys for index %s"
			status_exception::raise(Arg::PrivateDyn(119) << IDX.RDB$INDEX_NAME);
		}

		if (definition.refColumns.hasData())
		{
			// If referring columns count <> referred columns return error.

			if (definition.columns.getCount() != definition.refColumns.getCount())
			{
				// msg 133: "Number of referencing columns do not equal number of
				// referenced columns
				status_exception::raise(Arg::PrivateDyn(133));
			}

			// Lookup a unique index in the referenced relation with the
			// referenced fields mentioned.

			request2.reset(tdbb, drq_l_unq_idx, DYN_REQUESTS);

			MetaName indexName;
			int listIndex = -1;
			bool found = false;

			FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				RC IN RDB$RELATION_CONSTRAINTS CROSS
				IND IN RDB$INDICES OVER RDB$INDEX_NAME CROSS
				ISEG IN RDB$INDEX_SEGMENTS OVER RDB$INDEX_NAME
				WITH IND.RDB$RELATION_NAME EQ definition.refRelation.c_str() AND
					 IND.RDB$UNIQUE_FLAG NOT MISSING AND
					 (RC.RDB$CONSTRAINT_TYPE = PRIMARY_KEY OR
					  RC.RDB$CONSTRAINT_TYPE = UNIQUE_CNSTRT)
				SORTED BY IND.RDB$INDEX_NAME,
						  DESCENDING ISEG.RDB$FIELD_POSITION
			{
				if (indexName != IND.RDB$INDEX_NAME)
				{
					if (listIndex >= 0)
						found = false;
					if (found)
						break;
					listIndex = definition.refColumns.getCount() - 1;
					indexName = IND.RDB$INDEX_NAME;
					found = true;
				}

				// If there are no more fields or the field name doesn't
				// match, then this is not the correct index.

				if (listIndex >= 0)
				{
					fb_utils::exact_name_limit(ISEG.RDB$FIELD_NAME, sizeof(ISEG.RDB$FIELD_NAME));
					if (definition.refColumns[listIndex--] != ISEG.RDB$FIELD_NAME)
						found = false;
				}
				else
					found = false;
			}
			END_FOR

			if (listIndex >= 0)
				found = false;

			if (found)
			{
				IDX.RDB$FOREIGN_KEY.NULL = FALSE;
				strcpy(IDX.RDB$FOREIGN_KEY, indexName.c_str());

				if (referredIndexName)
					*referredIndexName = indexName;
			}
			else
			{
				AutoRequest request3;
				bool isView = false;

				FOR(REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
					X IN RDB$RELATIONS
					WITH X.RDB$RELATION_NAME EQ definition.refRelation.c_str()
				{
					found = true;
					isView = !X.RDB$VIEW_BLR.NULL;
				}
				END_FOR

				if (isView)
				{
					// msg 242: "attempt to reference a view (%s) in a foreign key"
					status_exception::raise(Arg::PrivateDyn(242) << definition.refRelation);
				}

				if (found)
				{
					// msg 18: "could not find UNIQUE or PRIMARY KEY constraint in table %s with
					// specified columns"
					status_exception::raise(Arg::PrivateDyn(18) << definition.refRelation);
				}
				else
				{
					// msg 241: "Table %s not found"
					status_exception::raise(Arg::PrivateDyn(241) << definition.refRelation);
				}
			}
		}
		else if (definition.refRelation.hasData())
		{
			request2.reset(tdbb, drq_l_primary, DYN_REQUESTS);

			FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				IND IN RDB$INDICES CROSS
				RC IN RDB$RELATION_CONSTRAINTS OVER RDB$INDEX_NAME
				WITH IND.RDB$RELATION_NAME EQ definition.refRelation.c_str() AND
					 RC.RDB$CONSTRAINT_TYPE EQ PRIMARY_KEY
			{
				// Number of columns in referred index should be same as number
				// of columns in referring index.

				fb_assert(IND.RDB$SEGMENT_COUNT >= 0);
				if (definition.columns.getCount() != ULONG(IND.RDB$SEGMENT_COUNT))
				{
					// msg 133: "Number of referencing columns do not equal number of
					// referenced columns"
					status_exception::raise(Arg::PrivateDyn(133));
				}

				fb_utils::exact_name_limit(IND.RDB$INDEX_NAME, sizeof(IND.RDB$INDEX_NAME));

				IDX.RDB$FOREIGN_KEY.NULL = FALSE;
				strcpy(IDX.RDB$FOREIGN_KEY, IND.RDB$INDEX_NAME);

				if (referredIndexName)
					*referredIndexName = IND.RDB$INDEX_NAME;
			}
			END_FOR

			if (IDX.RDB$FOREIGN_KEY.NULL)
			{
				// msg 20: "could not find PRIMARY KEY index in specified table %s"
				status_exception::raise(Arg::PrivateDyn(20) << definition.refRelation);
			}
		}

		IDX.RDB$SEGMENT_COUNT = SSHORT(definition.columns.getCount());
	}
	END_STORE
}


string CreateIndexNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, unique);
	NODE_PRINT(printer, descending);
	NODE_PRINT(printer, relation);
	NODE_PRINT(printer, columns);
	NODE_PRINT(printer, computed);

	return "CreateIndexNode";
}

void CreateIndexNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	const MetaName &relationName = relation->dsqlName;
	dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());
	SCL_check_relation(tdbb, &dscName, SCL_alter, false);
}

// Define an index.
void CreateIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_CREATE_INDEX,
		name, NULL);

	CreateIndexNode::Definition definition;
	definition.type = isc_dyn_def_idx;
	definition.relation = relation->dsqlName;
	definition.unique = unique;
	definition.descending = descending;
	definition.inactive = false;

	if (columns)
	{
	    const NestConst<ValueExprNode>* ptr = columns->items.begin();
	    const NestConst<ValueExprNode>* const end = columns->items.end();

		for (; ptr != end; ++ptr)
		{
			MetaName& column = definition.columns.add();
			column = nodeAs<FieldNode>(*ptr)->dsqlName;
		}
	}
	else if (computed)
	{
		string computedSource;
		BlrDebugWriter::BlrData computedValue;

		defineComputed(dsqlScratch, relation, NULL, computed, computedSource, computedValue);

		attachment->storeMetaDataBlob(tdbb, transaction, &definition.expressionSource,
			computedSource);
		attachment->storeBinaryBlob(tdbb, transaction, &definition.expressionBlr, computedValue);
	}

	store(tdbb, transaction, name, definition);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_CREATE_INDEX,
		name, NULL);

	savePoint.release();	// everything is ok
}


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


string AlterIndexNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, active);

	return "AlterIndexNode";
}

void AlterIndexNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	bool systemIndex;
	MetaName relationName = getIndexRelationName(tdbb, transaction, name, systemIndex);

	dsc dscName;
	dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());

	SCL_check_relation(tdbb, &dscName, SCL_alter, systemIndex);
}

void AlterIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_m_index, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES
		WITH IDX.RDB$INDEX_NAME EQ name.c_str()
	{
		found = true;

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX,
			name, NULL);

		MODIFY IDX
			IDX.RDB$INDEX_INACTIVE.NULL = FALSE;
			IDX.RDB$INDEX_INACTIVE = active ? FALSE : TRUE;
		END_MODIFY
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_INDEX,
			name, NULL);
	}
	else
	{
		// msg 48: "Index not found"
		status_exception::raise(Arg::PrivateDyn(48));
	}

	savePoint.release();	// everything is ok
}


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


string SetStatisticsNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);

	return "SetStatisticsNode";
}

void SetStatisticsNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	bool systemIndex;
	MetaName relationName = getIndexRelationName(tdbb, transaction, name, systemIndex);

	dsc dscName;
	dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());

	SCL_check_relation(tdbb, &dscName, SCL_alter, false);
}

void SetStatisticsNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_m_set_statistics, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES
		WITH IDX.RDB$INDEX_NAME EQ name.c_str()
	{
		found = true;

		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_ALTER_INDEX,
			name, NULL);

		MODIFY IDX
			// For V4 index selectivity can be set only to -1.
			IDX.RDB$STATISTICS.NULL = FALSE;
			IDX.RDB$STATISTICS = -1.0;
		END_MODIFY
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_ALTER_INDEX,
			name, NULL);
	}
	else
	{
		// msg 48: "Index not found"
		status_exception::raise(Arg::PrivateDyn(48));
	}

	savePoint.release();	// everything is ok
}


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


// Delete the records in RDB$INDEX_SEGMENTS pertaining to an index.
bool DropIndexNode::deleteSegmentRecords(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& name)
{
	AutoCacheRequest request(tdbb, drq_e_idx_segs, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDXSEG IN RDB$INDEX_SEGMENTS WITH IDXSEG.RDB$INDEX_NAME EQ name.c_str()
	{
		found = true;
		ERASE IDXSEG;
	}
	END_FOR

	return found;
}

string DropIndexNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);

	return "DropIndexNode";
}

void DropIndexNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	bool systemIndex;
	MetaName relationName = getIndexRelationName(tdbb, transaction, name, systemIndex);

	dsc dscName;
	dscName.makeText(relationName.length(), CS_METADATA, (UCHAR*) relationName.c_str());

	SCL_check_relation(tdbb, &dscName, SCL_alter, systemIndex);
}

void DropIndexNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_e_indices, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		IDX IN RDB$INDICES
		WITH IDX.RDB$INDEX_NAME EQ name.c_str()
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_DROP_INDEX, name, NULL);

		ERASE IDX;

		if (IDX.RDB$EXPRESSION_BLR.NULL && !deleteSegmentRecords(tdbb, transaction, name))
		{
			// msg 50: "No segments found for index"
			status_exception::raise(Arg::PrivateDyn(50));
		}

		found = true;
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_INDEX,
			name, NULL);
	}
	else
	{
		// msg 48: "Index not found"
		status_exception::raise(Arg::PrivateDyn(48));
	}

	savePoint.release();	// everything is ok
}


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


string CreateFilterNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, inputFilter);
	NODE_PRINT(printer, outputFilter);
	NODE_PRINT(printer, entryPoint);
	NODE_PRINT(printer, moduleName);

	return "CreateFilterNode";
}

void CreateFilterNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_create_access(tdbb, SCL_object_filter);
}

// Define a blob filter.
void CreateFilterNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
	Attachment* const attachment = transaction->tra_attachment;
	const MetaString& ownerName = attachment->getEffectiveUserName();

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	/***
	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DECLARE_FILTER,
		name, NULL);
	***/

	AutoCacheRequest request(tdbb, drq_s_filters, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$FILTERS
	{
		strcpy(X.RDB$FUNCTION_NAME, name.c_str());
		strcpy(X.RDB$OWNER_NAME, ownerName.c_str());
		X.RDB$SYSTEM_FLAG = 0;
		moduleName.copyTo(X.RDB$MODULE_NAME, sizeof(X.RDB$MODULE_NAME));
		entryPoint.copyTo(X.RDB$ENTRYPOINT, sizeof(X.RDB$ENTRYPOINT));

		if (inputFilter->name.hasData())
		{
			if (!METD_get_type(transaction, inputFilter->name, "RDB$FIELD_SUB_TYPE",
					&X.RDB$INPUT_SUB_TYPE))
			{
				status_exception::raise(
					Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
					Arg::Gds(isc_dsql_datatype_err) <<
					Arg::Gds(isc_dsql_blob_type_unknown) << inputFilter->name);
			}
		}
		else
			X.RDB$INPUT_SUB_TYPE = inputFilter->number;

		if (outputFilter->name.hasData())
		{
			if (!METD_get_type(transaction, outputFilter->name, "RDB$FIELD_SUB_TYPE",
					&X.RDB$OUTPUT_SUB_TYPE))
			{
				status_exception::raise(
					Arg::Gds(isc_sqlerr) << Arg::Num(-204) <<
					Arg::Gds(isc_dsql_datatype_err) <<
					Arg::Gds(isc_dsql_blob_type_unknown) << outputFilter->name);
			}
		}
		else
			X.RDB$OUTPUT_SUB_TYPE = outputFilter->number;
	}
	END_STORE

	/***
	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DECLARE_FILTER,
		name, NULL);
	***/

	savePoint.release();	// everything is ok
}


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


string DropFilterNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);

	return "DropFilterNode";
}

void DropFilterNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_filter(tdbb, name, SCL_drop);
}

void DropFilterNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_e_filters, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$FILTERS
		WITH X.RDB$FUNCTION_NAME EQ name.c_str()
	{
		ERASE X;
		found = true;
	}
	END_FOR

	if (!found)
	{
		// msg 37: "Blob Filter %s not found"
		status_exception::raise(Arg::PrivateDyn(37) << name);
	}

	savePoint.release();	// everything is ok
}


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


string CreateShadowNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, number);
	NODE_PRINT(printer, manual);
	NODE_PRINT(printer, conditional);
	NODE_PRINT(printer, firstLength);
	NODE_PRINT(printer, files);

	return "CreateShadowNode";
}

void CreateShadowNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_database(tdbb, SCL_alter);
}

void CreateShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
	// Should be caught by the parser.
	if (number == 0)
	{
		status_exception::raise(
			Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
			Arg::Gds(isc_dsql_command_err) <<
			Arg::Gds(isc_dsql_shadow_number_err));
	}

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	// If a shadow set identified by the shadow number already exists return error.

	AutoCacheRequest request(tdbb, drq_l_shadow, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIRST 1 X IN RDB$FILES
		WITH X.RDB$SHADOW_NUMBER EQ number
	{
		// msg 165: "Shadow %ld already exists"
		status_exception::raise(Arg::PrivateDyn(165) << Arg::Num(number));
	}
	END_FOR

	SLONG start = 0;

	for (NestConst<DbFileClause>* i = files.begin(); i != files.end(); ++i)
	{
		bool first = i == files.begin();
		DbFileClause* file = *i;

		if (!first && i[-1]->length == 0 && file->start == 0)
		{
			// Preceding file did not specify length, so %s must include starting page number
			status_exception::raise(
				Arg::Gds(isc_sqlerr) << Arg::Num(-607) <<
				Arg::Gds(isc_dsql_command_err) <<
				Arg::Gds(isc_dsql_file_length_err) << file->name);
		}

		defineFile(tdbb, transaction, number, manual && first, conditional && first,
			start, file->name.c_str(), file->start, file->length);
	}

	savePoint.release();	// everything is ok
}


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


string DropShadowNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, number);
	NODE_PRINT(printer, nodelete);

	return "DropShadowNode";
}

void DropShadowNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_database(tdbb, SCL_alter);
}

void DropShadowNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_e_shadow, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIL IN RDB$FILES
		WITH FIL.RDB$SHADOW_NUMBER EQ number
	{
		if (nodelete)
		{
			AutoSetRestoreFlag<ULONG> noDfw(&tdbb->tdbb_flags, TDBB_dont_post_dfw, true);
			MODIFY FIL USING
				FIL.RDB$FILE_FLAGS |= FILE_nodelete;
			END_MODIFY
		}
		ERASE FIL;
	}
	END_FOR

	// ASF: No error is raised if the shadow is not found.

	savePoint.release();	// everything is ok
}


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


USHORT PrivilegesNode::convertPrivilegeFromString(thread_db* tdbb, jrd_tra* transaction, MetaName privilege)
{
	string priv(privilege.c_str());
	priv.upper();

	return SCL_convert_privilege(tdbb, transaction, priv);
}


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


string CreateAlterRoleNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);

	return "CreateAlterRoleNode";
}

void CreateAlterRoleNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	if (createFlag)
		SCL_check_create_access(tdbb, SCL_object_role);
	else
		SCL_check_role(tdbb, name, SCL_alter);
}

void CreateAlterRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	if (createFlag && sysPrivDrop)
	{
		// msg 293: DROP SYSTEM PRIVILEGES should not be used in CREATE ROLE
		Arg::PrivateDyn(293).raise();
	}

	Attachment* const attachment = transaction->tra_attachment;
	const MetaString& ownerName = attachment->getEffectiveUserName();

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
		createFlag ? DDL_TRIGGER_CREATE_ROLE : DDL_TRIGGER_ALTER_ROLE, name, NULL);

	if (name == ownerName)
	{
		// msg 193: "user name @1 could not be used for SQL role"
		status_exception::raise(Arg::PrivateDyn(193) << ownerName);
	}

	if (name == NULL_ROLE)
	{
		// msg 195: "keyword @1 could not be used as SQL role name"
		status_exception::raise(Arg::PrivateDyn(195) << name);
	}

	if (isItUserName(tdbb, transaction))
	{
		// msg 193: "user name @1 could not be used for SQL role"
		status_exception::raise(Arg::PrivateDyn(193) << name);
	}

	MetaName dummyName;
	if (createFlag && isItSqlRole(tdbb, transaction, name, dummyName))
	{
		// msg 194: "SQL role @1 already exists"
		status_exception::raise(Arg::PrivateDyn(194) << name);
	}

	if (privileges.hasData() || sysPrivDrop)
	{
		if (!tdbb->getAttachment()->locksmith(tdbb, CREATE_PRIVILEGED_ROLES))
		{
			// msg 294: Access to SYSTEM PRIVILEGES in ROLES denied to @1
			(Arg::PrivateDyn(294) << ownerName).raise();
		}
	}

	UserId::Privileges newPrivileges;
	if (privileges.hasData() && !sysPrivDrop)
	{
		const MetaName* const end = privileges.end();
		for (const MetaName* privName = privileges.begin(); privName < end; ++privName)
			newPrivileges.set(convertPrivilegeFromString(tdbb, transaction, *privName));
	}

	string p;
	newPrivileges.store(p.getBuffer(newPrivileges.BYTES_COUNT));

	if (createFlag)
	{
		PreparedStatement::Builder sql;
		sql << "insert into rdb$roles(rdb$role_name, rdb$owner_name, rdb$system_privileges, rdb$system_flag)"
			<< "values(" << name << "," << ownerName << "," << p << ", 0)";

		AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql));
		ps->execute(tdbb, transaction);
	}
	else if (privileges.hasData() || sysPrivDrop)
	{
		PreparedStatement::Builder sql;
		sql << "update rdb$roles set rdb$system_privileges =" << p << "where rdb$role_name =" << name;

		AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql));
		if (ps->executeUpdate(tdbb, transaction) == 0)
		{
			// msg 155: "Role %s not found"
			(Arg::PrivateDyn(155) << name).raise();
		}
	}

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER,
		createFlag ? DDL_TRIGGER_CREATE_ROLE : DDL_TRIGGER_ALTER_ROLE, name, NULL);

	savePoint.release();	// everything is ok
}

// If role name is user name returns true. Otherwise returns false.
bool CreateAlterRoleNode::isItUserName(thread_db* tdbb, jrd_tra* transaction)
{
	bool found = false;

	// If there is a user with privilege or a grantor on a relation we
	// can infer there is a user with this name

	AutoCacheRequest request(tdbb, drq_get_user_priv, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		WITH (PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_user) OR
			 (PRIV.RDB$GRANTOR EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_relation)
	{
		found = true;
	}
	END_FOR

	if (found)
		return found;

	// We can infer that 'role name' is a user name if it owns any relations
	// Note we can only get here if a user creates a table and revokes all
	// his privileges on the table

	request.reset(tdbb, drq_get_rel_owner, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		REL IN RDB$RELATIONS
		WITH REL.RDB$OWNER_NAME EQ name.c_str()
	{
		found = true;
	}
	END_FOR

	return found;
}


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


string MappingNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, plugin);
	NODE_PRINT(printer, db);
	NODE_PRINT(printer, fromType);
	NODE_PRINT(printer, from);
	NODE_PRINT(printer, to);
	NODE_PRINT(printer, op);
	NODE_PRINT(printer, mode);
	NODE_PRINT(printer, global);
	NODE_PRINT(printer, role);

	return "MappingNode";
}

void MappingNode::validateAdmin()
{
	if (to && (*to != ADMIN_ROLE))
		Arg::Gds(isc_alter_role).raise();
}

// add some item to DDL in quotes
void MappingNode::addItem(string& ddl, const char* text, char quote)
{
	ddl += quote;
	char c;

	while ((c = *text++))
	{
		ddl += c;
		if (c == quote)
			ddl += c;
	}

	ddl += quote;
}

void MappingNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	//DDL_TODO
}

void MappingNode::runInSecurityDb(SecDbContext* secDbContext)
{
	Firebird::LocalStatus st;
	CheckStatusWrapper statusWrapper(&st);

	// first of all try to use regenerated DDL statement
	// that's the best way if security database is FB3 or higher fb version
	string ddl;

	switch(op)
	{
	case MAP_ADD:
		ddl = "CREATE MAPPING ";
		break;
	case MAP_MOD:
		ddl = "ALTER MAPPING ";
		break;
	case MAP_DROP:
		ddl = "DROP MAPPING ";
		break;
	case MAP_RPL:
		ddl = "CREATE OR ALTER MAPPING ";
		break;
	}

	addItem(ddl, name.c_str());
	if (op != MAP_DROP)
	{
		ddl += " USING ";
		switch (mode)
		{
		case 'P':
			if (!plugin)
				ddl += "ANY PLUGIN ";
			else
			{
				ddl += "PLUGIN ";
				addItem(ddl, plugin->c_str());
				ddl += ' ';
			}
			break;
		case 'S':
			ddl += "ANY PLUGIN SERVERWIDE ";
			break;
		case '*':
			ddl += "* ";
			break;
		case 'M':
			ddl += "MAPPING ";
			break;
		}

		if (db)
		{
			ddl += "IN ";
			addItem(ddl, db->c_str());
			ddl += ' ';
		}

		if (fromType)
		{
			ddl += "FROM ";
			if (!from)
				ddl += "ANY ";
			addItem(ddl, fromType->c_str());
			ddl += ' ';
			if (from)
			{
				ddl += "_utf8 ";
				addItem(ddl, fromUtf8.c_str(), '\'');
				ddl += ' ';
			}
		}

		ddl += "TO ";
		ddl += (role ? "ROLE" : "USER");
		if (to)
		{
			ddl += ' ';
			addItem(ddl, to->c_str());
		}
	}

	// Now try to run DDL
	secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, ddl.c_str(), SQL_DIALECT_V6,
		NULL, NULL, NULL, NULL);

	if (statusWrapper.getState() & IStatus::STATE_ERRORS)
	{
		try
		{
			// try direct access to rdb$auth_mapping table in secure db
			LocalStatus s2;
			CheckStatusWrapper statusWrapper2(&s2);

			// check presence of such record in the table
			Message msgCheck;
			Field<Varying> nm(msgCheck, MAX_SQL_IDENTIFIER_LEN);
			nm = name.c_str();

			Message result;
			Field<ISC_INT64> cnt(result);

			const char* checkSql = "select count(*) from RDB$AUTH_MAPPING where RDB$MAP_NAME = ?";

			secDbContext->att->execute(&statusWrapper2, secDbContext->tra, 0, checkSql, SQL_DIALECT_V6,
				msgCheck.getMetadata(), msgCheck.getBuffer(), result.getMetadata(), result.getBuffer());
			check(&statusWrapper2);

			if (cnt > 1 && op != MAP_DROP)
				ERRD_bugcheck("Database mapping misconfigured");

			bool hasLine = cnt > 0;
			switch(op)
			{
			case MAP_ADD:
				if (hasLine)
					(Arg::Gds(isc_map_already_exists) << name).raise();
				break;

			case MAP_MOD:
			case MAP_DROP:
				if (!hasLine)
					(Arg::Gds(isc_map_not_exists) << name).raise();
				break;

			case MAP_RPL:
				op = hasLine ? MAP_MOD : MAP_DROP;
				break;
			}

			// Get ready to modify table
			Message full;
			Field<ISC_SHORT> toType(full);
			Field<Varying> t(full, MAX_SQL_IDENTIFIER_LEN);
			Field<Varying> usng2(full, 1);
			Field<Varying> plug2(full, MAX_SQL_IDENTIFIER_LEN);
			Field<Varying> d2(full, MAX_SQL_IDENTIFIER_LEN);
			Field<Varying> type2(full, MAX_SQL_IDENTIFIER_LEN);
			Field<Varying> f2(full, 255);
			Field<Varying> nm2(full, MAX_SQL_IDENTIFIER_LEN);

			toType = role ? 1 : 0;
			if (to)
				t = to->c_str();
			usng2.set(1, &mode);
			if (plugin)
				plug2 = plugin->c_str();
			if (db)
				d2 = db->c_str();
			if (fromType)
				type2 = fromType->c_str();
			if (from)
				f2 = fromUtf8.c_str();
			nm2 = name.c_str();

			Message* msg = NULL;
			const char* sql = NULL;
			switch(op)
			{
			case MAP_ADD:
				sql = "insert into RDB$AUTH_MAPPING(RDB$MAP_TO_TYPE, RDB$MAP_TO, RDB$MAP_USING, "
					"RDB$MAP_PLUGIN, RDB$MAP_DB, RDB$MAP_FROM_TYPE, RDB$MAP_FROM, RDB$MAP_NAME, RDB$SYSTEM_FLAG) "
					"values (?, ?, ?, ?, ?, ?, ?, ?, 0)";
				msg = &full;
				break;
			case MAP_MOD:
				sql = "update RDB$AUTH_MAPPING set RDB$MAP_TO_TYPE = ?, RDB$MAP_TO = ?, "
					"RDB$MAP_USING = ?, RDB$MAP_PLUGIN = ?, RDB$MAP_DB = ?, "
					"RDB$MAP_FROM_TYPE = ?, RDB$MAP_FROM = ? "
					"where RDB$MAP_NAME = ?";
				msg = &full;
				break;
			case MAP_DROP:
				sql = "delete from RDBAUTH_MAPPING where RDB$MAP_NAME = ?";
				msg = &msgCheck;
				break;
			}

			// Actual modification
			fb_assert(sql && msg);
			secDbContext->att->execute(&statusWrapper2, secDbContext->tra, 0, sql, SQL_DIALECT_V6,
				msg->getMetadata(), msg->getBuffer(), NULL, NULL);
			check(&statusWrapper2);
		}
		catch (const Exception&)
		{
			if (st.getState() & IStatus::STATE_ERRORS)
			{
				const ISC_STATUS* stat2 = st.getErrors();
				if (stat2[1] != isc_dsql_token_unk_err)
					status_exception::raise(&st);
			}

			throw;
		}
	}
}

// It's purpose is to add/drop mapping from any security name to DB security object.
void MappingNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	if (!(tdbb->getAttachment() && tdbb->getAttachment()->locksmith(tdbb, CHANGE_MAPPING_RULES)))
		(Arg::Gds(isc_adm_task_denied) << Arg::Gds(isc_miss_prvlg) << "CHANGE_MAPPING_RULES").raise();

	if (from)
		fromUtf8 = from->toUtf8(dsqlScratch);

	if (global)
	{
		executeInSecurityDb(transaction);
		return;
	}

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	fb_assert(op == MAP_DROP || fromType);

	short plugNull = plugin ? FALSE  : TRUE;
	short dbNull = db ? FALSE : TRUE;
	short fromNull = from ? FALSE : TRUE;

	char usingText[2];
	usingText[0] = mode;
	usingText[1] = '\0';

	AutoCacheRequest request1(tdbb, drq_map_mod, DYN_REQUESTS);
	bool found = false;
	int ddlTriggerAction = 0;

	FOR(REQUEST_HANDLE request1 TRANSACTION_HANDLE transaction)
		M IN RDB$AUTH_MAPPING
		WITH M.RDB$MAP_NAME EQ name.c_str()
	{
		found = true;
		switch (op)
		{
		case MAP_ADD:
			break;

		case MAP_MOD:
		case MAP_RPL:
			ddlTriggerAction = DDL_TRIGGER_ALTER_MAPPING;
			executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction,
				name, NULL);

			MODIFY M
				if (to)
				{
					M.RDB$MAP_TO.NULL = FALSE;
					strcpy(M.RDB$MAP_TO, to->c_str());
				}
				else
					M.RDB$MAP_TO.NULL = TRUE;
				M.RDB$MAP_TO_TYPE = role ? 1 : 0;

				strcpy(M.RDB$MAP_USING, usingText);
				M.RDB$MAP_PLUGIN.NULL = plugNull;
				if (!plugNull)
					strcpy(M.RDB$MAP_PLUGIN, plugin->c_str());

				M.RDB$MAP_DB.NULL = dbNull;
				if (!dbNull)
					strcpy(M.RDB$MAP_DB, db->c_str());

				strcpy(M.RDB$MAP_FROM_TYPE, fromType->c_str());
				M.RDB$MAP_FROM.NULL = fromNull;
				if (!fromNull)
					strcpy(M.RDB$MAP_FROM, fromUtf8.c_str());
			END_MODIFY
			break;

		case MAP_DROP:
			ddlTriggerAction = DDL_TRIGGER_DROP_MAPPING;
			executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction,
				name, NULL);

			ERASE M;
			break;
		}
	}
	END_FOR

	AutoCacheRequest request2(tdbb, drq_map_sto, DYN_REQUESTS);
	switch (op)
	{
	case MAP_ADD:
		if (found)
		{
			(Arg::Gds(isc_map_already_exists) << name).raise();
		}
		// fall through ...

	case MAP_RPL:
		if (found)
			break;

		ddlTriggerAction = DDL_TRIGGER_CREATE_MAPPING;
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlTriggerAction, name, NULL);

		STORE(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
		M IN RDB$AUTH_MAPPING
		{
			strcpy(M.RDB$MAP_NAME, name.c_str());
			strcpy(M.RDB$MAP_USING, usingText);
			M.RDB$MAP_PLUGIN.NULL = plugNull;
			if (!plugNull)
				strcpy(M.RDB$MAP_PLUGIN, plugin->c_str());

			M.RDB$MAP_DB.NULL = dbNull;
			if (!dbNull)
				strcpy(M.RDB$MAP_DB, db->c_str());

			strcpy(M.RDB$MAP_FROM_TYPE, fromType->c_str());
			M.RDB$MAP_FROM.NULL = fromNull;
			if (!fromNull)
				strcpy(M.RDB$MAP_FROM, fromUtf8.c_str());

			M.RDB$MAP_TO_TYPE = role ? 1 : 0;
			if (to)
			{
				M.RDB$MAP_TO.NULL = FALSE;
				strcpy(M.RDB$MAP_TO, to->c_str());
			}
			else
				M.RDB$MAP_TO.NULL = TRUE;

			M.RDB$SYSTEM_FLAG.NULL = FALSE;
			M.RDB$SYSTEM_FLAG = 0;
		}
		END_STORE
		break;

	case MAP_MOD:
	case MAP_DROP:
		if (!found)
			(Arg::Gds(isc_map_not_exists) << name).raise();
		break;
	}

	fb_assert(ddlTriggerAction > 0);
	if (ddlTriggerAction > 0)
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlTriggerAction, name, NULL);

	DFW_post_work(transaction, dfw_clear_cache, NULL, Mapping::MAPPING_CACHE);
	savePoint.release();	// everything is ok
}


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


string DropRoleNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, name);

	return "DropRoleNode";
}

void DropRoleNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_role(tdbb, name, SCL_drop);
}

void DropRoleNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	AutoCacheRequest request(tdbb, drq_drop_role, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		ROL IN RDB$ROLES
		WITH ROL.RDB$ROLE_NAME EQ name.c_str()
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE,
			DDL_TRIGGER_DROP_ROLE, name, NULL);

		if (ROL.RDB$SYSTEM_FLAG != 0)
		{
			// msg 284: can not drop system SQL role @1
			status_exception::raise(Arg::PrivateDyn(284) << name);
		}

		AutoCacheRequest request2(tdbb, drq_del_role_1, DYN_REQUESTS);

		// The first OR clause finds all members of the role.
		// The 2nd OR clause finds all privileges granted to the role
		FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
			PRIV IN RDB$USER_PRIVILEGES
			WITH ((PRIV.RDB$RELATION_NAME EQ name.c_str() AND PRIV.RDB$OBJECT_TYPE = obj_sql_role) OR
				  (PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_sql_role)) AND
				 PRIV.RDB$GRANTOR NOT MISSING
		{
			ERASE PRIV;
		}
		END_FOR

		AutoCacheRequest request3(tdbb, drq_del_role_2, DYN_REQUESTS);

		FOR(REQUEST_HANDLE request3 TRANSACTION_HANDLE transaction)
			DBC IN RDB$DB_CREATORS
			WITH DBC.RDB$USER EQ name.c_str() AND DBC.RDB$USER_TYPE = obj_sql_role
		{
			ERASE DBC;
		}
		END_FOR

		ERASE ROL;

		found = true;

		if (!ROL.RDB$SECURITY_CLASS.NULL)
			deleteSecurityClass(tdbb, transaction, ROL.RDB$SECURITY_CLASS);
	}
	END_FOR

	if (found)
	{
		executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_ROLE,
			name, NULL);
	}
	else
	{
		// msg 155: "Role %s not found"
		status_exception::raise(Arg::PrivateDyn(155) << name);
	}

	savePoint.release();	// everything is ok
}


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


MetaName UserNode::upper(const MetaName& str)
{
	return str;
}


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


string CreateAlterUserNode::internalPrint(NodePrinter& printer) const
{
	UserNode::internalPrint(printer);

	//// FIXME-PRINT: NODE_PRINT(printer, properties);
	NODE_PRINT(printer, name);
	NODE_PRINT(printer, password);
	NODE_PRINT(printer, firstName);
	NODE_PRINT(printer, middleName);
	NODE_PRINT(printer, lastName);
	NODE_PRINT(printer, plugin);
	NODE_PRINT(printer, comment);
	NODE_PRINT(printer, adminRole);
	NODE_PRINT(printer, active);
	NODE_PRINT(printer, mode);

	return "CreateAlterUserNode";
}


static void setCharField(Auth::CharField& field, const string* value)
{
	if (value)
	{
		LocalStatus s;
		CheckStatusWrapper statusWrapper(&s);

		if (value->hasData())
		{
			field.set(&statusWrapper, value->c_str());
			check(&statusWrapper);
			field.setEntered(&statusWrapper, 1);
			check(&statusWrapper);
		}
		else
		{
			field.setEntered(&statusWrapper, 0);
			check(&statusWrapper);
			field.setSpecified(1);
		}
	}
}


void CreateAlterUserNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	//DDL_TODO
}


void CreateAlterUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	if (mode != USER_ADD)
	{
		if (!password && !firstName && !middleName && !lastName &&
			!adminRole.specified && !active.specified && !comment && !properties.hasData())
		{
			// 283: ALTER USER requires at least one clause to be specified
			status_exception::raise(Arg::PrivateDyn(283));
		}
	}
	else if (!password)
	{
		// 291: CREATE USER requires password to be specified
		status_exception::raise(Arg::PrivateDyn(291));
	}

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	Auth::DynamicUserData* userData = FB_NEW_POOL(*transaction->tra_pool) Auth::DynamicUserData;

	MetaName text(name);
	if (text.isEmpty() && mode == USER_MOD)
	{
		// alter current user
		text = tdbb->getAttachment()->getUserName();

		if (text.isEmpty())
			(Arg::Gds(isc_random) << "Missing user name for ALTER CURRENT USER").raise();
	}

	Firebird::LocalStatus s;
	CheckStatusWrapper statusWrapper(&s);

	userData->op = mode == USER_ADD ? Auth::ADD_OPER : mode == USER_MOD ?
		Auth::MOD_OPER : Auth::ADDMOD_OPER;
	userData->user.set(&statusWrapper, text.c_str());
	check(&statusWrapper);
	userData->user.setEntered(&statusWrapper, 1);
	check(&statusWrapper);

	if (password)
	{
		if (password->isEmpty())
		{
			// 250: Password should not be empty string
			status_exception::raise(Arg::PrivateDyn(250));
		}

		setCharField(userData->pass, password);
	}

	setCharField(userData->first, firstName);
	setCharField(userData->middle, middleName);
	setCharField(userData->last, lastName);
	setCharField(userData->com, comment);
	if (plugin)
		userData->plugin = *plugin;

	if (adminRole.specified)
	{
		userData->adm.set(&statusWrapper, adminRole.value);
		check(&statusWrapper);
		userData->adm.setEntered(&statusWrapper, 1);
		check(&statusWrapper);
	}

	if (active.specified)
	{
		userData->act.set(&statusWrapper, (int) active.value);
		check(&statusWrapper);
		userData->act.setEntered(&statusWrapper, 1);
		check(&statusWrapper);
	}

	string attributesBuffer;

	for (unsigned cnt = 0; cnt < properties.getCount(); ++cnt)
	{
		if (mode != USER_ADD || properties[cnt].value.hasData())
		{
			string attribute;
			attribute.printf("%s=%s\n", properties[cnt].property.c_str(), properties[cnt].value.c_str());
			attributesBuffer += attribute;
		}
	}

	if (attributesBuffer.hasData())
	{
		userData->attr.set(&statusWrapper, attributesBuffer.c_str());
		check(&statusWrapper);
		userData->attr.setEntered(&statusWrapper, 1);
		check(&statusWrapper);
	}

	const int ddlAction = mode == USER_ADD ? DDL_TRIGGER_CREATE_USER : DDL_TRIGGER_ALTER_USER;

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, ddlAction,
		userData->user.get(), NULL);

	const USHORT id = transaction->getUserManagement()->put(userData);
	DFW_post_work(transaction, dfw_user_management, NULL, id);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, ddlAction,
		userData->user.get(), NULL);

	savePoint.release();	// everything is ok
}


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


string DropUserNode::internalPrint(NodePrinter& printer) const
{
	UserNode::internalPrint(printer);

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, plugin);
	NODE_PRINT(printer, silent);

	return "DropUserNode";
}

void DropUserNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	//DDL_TODO
}

void DropUserNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	Auth::DynamicUserData* userData = FB_NEW_POOL(*transaction->tra_pool) Auth::DynamicUserData;

	string text = name.c_str();

	Firebird::LocalStatus s;
	CheckStatusWrapper statusWrapper(&s);

	userData->op = Auth::DEL_OPER;
	userData->user.set(&statusWrapper, text.c_str());
	userData->user.setEntered(&statusWrapper, 1);
	userData->plugin = plugin;
	userData->silent = silent;
	check(&statusWrapper);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_BEFORE, DDL_TRIGGER_DROP_USER,
		userData->user.get(), NULL);

	const USHORT id = transaction->getUserManagement()->put(userData);
	DFW_post_work(transaction, dfw_user_management, NULL, id);

	executeDdlTrigger(tdbb, dsqlScratch, transaction, DTW_AFTER, DDL_TRIGGER_DROP_USER,
		userData->user.get(), NULL);

	savePoint.release();	// everything is ok
}


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


string GrantRevokeNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, isGrant);
	NODE_PRINT(printer, privileges);
	NODE_PRINT(printer, roles);
	NODE_PRINT(printer, object);
	NODE_PRINT(printer, users);
	NODE_PRINT(printer, grantAdminOption);
	NODE_PRINT(printer, grantor);
	NODE_PRINT(printer, isDdl);

	return "GrantRevokeNode";
}

void GrantRevokeNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	// GRANT OPTION will be checked in grantRevoke method
}

void GrantRevokeNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction)
{
	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	createDbJobs.clear();

	const GranteeClause* usersPtr;
	const GranteeClause* usersEnd;

	if (!isGrant && roles.isEmpty() && privileges.isEmpty() && !object)	// REVOKE ALL ON ALL
	{
		usersEnd = users.end();
		for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
			grantRevoke(tdbb, transaction, NULL, usersPtr, NULL, NULL, 0);
	}
	else
	{
		SSHORT option = 0; // no grant/admin option

		if (roles.isEmpty())
		{
			if (grantAdminOption)
				option = 1; // with grant option

			usersEnd = users.end();
			for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
				modifyPrivileges(tdbb, transaction, option, usersPtr);
		}
		else
		{
			if (grantAdminOption)
				option = 2; // with admin option

			const GranteeClause* rolesPtr = roles.begin();
			const GranteeClause* rolesEnd = roles.end();
			const bool* defaultRolesPtr = defaultRoles.begin();
			for (; rolesPtr != rolesEnd; ++rolesPtr, ++defaultRolesPtr)
			{
				usersEnd = users.end();
				const bool defaultRole = *defaultRolesPtr;
				for (usersPtr = users.begin(); usersPtr != usersEnd; ++usersPtr)
					grantRevoke(tdbb, transaction, rolesPtr, usersPtr, "M", defaultRole ? "D" : NULL, option);
			}

			// Invalidate system privileges cache
			DFW_post_work(transaction, dfw_clear_cache, NULL, Mapping::SYSTEM_PRIVILEGES_CACHE);
		}
	}

	if (createDbJobs.hasData())
		executeInSecurityDb(transaction);

	savePoint.release();	// everything is ok
}

void GrantRevokeNode::runInSecurityDb(SecDbContext* secDbContext)
{
	for (unsigned n = 0; n < createDbJobs.getCount(); ++n)
	{
		CreateDbJob& j = createDbJobs[n];
		LocalStatus st;
		CheckStatusWrapper statusWrapper(&st);

		Message result;
		Field<ISC_INT64> cnt(result);

		if (j.userType == obj_sql_role && (!j.allOnAll))
		{
			Message isRole;
			Field<Varying> u(isRole, MAX_SQL_IDENTIFIER_LEN);
			u = j.user.c_str();

			const char* isRoleSql = "select count(*) from RDB$ROLES where RDB$ROLE_NAME = ?";
			secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, isRoleSql, SQL_DIALECT_V6,
				isRole.getMetadata(), isRole.getBuffer(), result.getMetadata(), result.getBuffer());
			check(&statusWrapper);

			if (cnt == 0)
			{
				// msg 188: Role doesn't exist.
				status_exception::raise(Arg::PrivateDyn(188) << j.user <<
					Arg::Gds(isc_random) << "in security database");
			}
		}

		Message gr;
		Field<ISC_SHORT> uType(gr);
		Field<Varying> u(gr, MAX_SQL_IDENTIFIER_LEN);
		uType = j.userType;
		u = j.user.c_str();

		const char* checkSql = "select count(*) from RDB$DB_CREATORS where RDB$USER_TYPE = ? and RDB$USER = ?";
		secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, checkSql, SQL_DIALECT_V6,
			gr.getMetadata(), gr.getBuffer(), result.getMetadata(), result.getBuffer());
		check(&statusWrapper);

		if (isGrant)
		{
			if (!cnt)
			{
				const char* insertSql = "insert into RDB$DB_CREATORS(RDB$USER_TYPE, RDB$USER) values(?, ?)";
				secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, insertSql, SQL_DIALECT_V6,
					gr.getMetadata(), gr.getBuffer(), NULL, NULL);
				check(&statusWrapper);
			}
		}
		else
		{
			if (cnt)
			{
				const char* deleteSql = "delete from RDB$DB_CREATORS where RDB$USER_TYPE = ? and RDB$USER = ?";
				secDbContext->att->execute(&statusWrapper, secDbContext->tra, 0, deleteSql, SQL_DIALECT_V6,
					gr.getMetadata(), gr.getBuffer(), NULL, NULL);

				j.grantErased = true;
			}

			if (!j.grantErased)
			{
				if (j.allOnAll)
				{
					const char* all = "ALL";

					if (j.badGrantor)
					{
						// msg 246: @1 is not grantor of @2 on @3 to @4.
						(Arg::PrivateDyn(246) << j.revoker.c_str() << all << all << j.user).raise();
					}

					// msg 247: Warning: @1 on @2 is not granted to @3.
					ERR_post_warning(
						Arg::Warning(isc_dyn_miss_priv_warning) <<
						all << all << j.user);
				}
				else
				{
					// msg 247: Warning: @1 on @2 is not granted to @3.
					ERR_post_warning(Arg::Warning(isc_dyn_miss_priv_warning) <<
						privilegeName('C') << "DATABASE" << j.user);
				}
			}
		}
	}
}

void GrantRevokeNode::modifyPrivileges(thread_db* tdbb, jrd_tra* transaction, SSHORT option,
	const GranteeClause* user)
{
	string privs;

	for (PrivilegeClause* i = privileges.begin(); i != privileges.end(); ++i)
	{
		if (i->first == 'A')
			grantRevoke(tdbb, transaction, object, user, "A", NULL, option);
		else if (i->second)
		{
			char privs0[2] = {i->first, '\0'};

			ValueListNode* fields = i->second;

			for (NestConst<ValueExprNode>* ptr = fields->items.begin(); ptr != fields->items.end(); ++ptr)
			{
				grantRevoke(tdbb, transaction, object, user, privs0,
					nodeAs<FieldNode>(*ptr)->dsqlName, option);
			}
		}
		else
			privs += i->first;
	}

	if (privs.hasData())
		grantRevoke(tdbb, transaction, object, user, privs.c_str(), NULL, option);
}


static bool checkObjectExist(thread_db* tdbb, jrd_tra* transaction, const MetaName& name, int type)
{
	bool rc = false;

	switch (type)
	{
		case obj_procedure:
		{
			AutoCacheRequest request(tdbb, drq_proc_exist, DYN_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$PROCEDURES
				WITH X.RDB$PROCEDURE_NAME EQ name.c_str() AND X.RDB$PACKAGE_NAME MISSING
			{
				rc = true;
			}
			END_FOR
			break;
		}

		case obj_udf:
		{
			AutoCacheRequest request(tdbb, drq_udf_exist, DYN_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$FUNCTIONS
				WITH X.RDB$FUNCTION_NAME EQ name.c_str() AND X.RDB$PACKAGE_NAME MISSING
			{
				rc = true;
			}
			END_FOR
			break;
		}

		case obj_package_header:
		{
			AutoCacheRequest request(tdbb, drq_package_exist, DYN_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$PACKAGES
				WITH X.RDB$PACKAGE_NAME EQ name.c_str()
			{
				rc = true;
			}
			END_FOR
			break;
		}

		case obj_trigger:
		{
			AutoCacheRequest request(tdbb, drq_trigger_exist, DYN_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$TRIGGERS
				WITH X.RDB$TRIGGER_NAME EQ name.c_str()
			{
				rc = true;
			}
			END_FOR
			break;
		}

		case obj_relation:
		case obj_view:
		{
			AutoCacheRequest request(tdbb, drq_rel_exist, DYN_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$RELATIONS
				WITH X.RDB$RELATION_NAME EQ name.c_str()
			{
				rc = (type != obj_view) || !X.RDB$VIEW_BLR.NULL;
			}
			END_FOR
			break;
		}

		case obj_exception:
		{
			AutoCacheRequest request(tdbb, drq_exception_exist, DYN_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$EXCEPTIONS
				WITH X.RDB$EXCEPTION_NAME EQ name.c_str()
			{
				rc = true;
			}
			END_FOR
			break;
		}

		case obj_generator:
		{
			AutoCacheRequest request(tdbb, drq_generator_exist, DYN_REQUESTS);
			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				X IN RDB$GENERATORS
				WITH X.RDB$GENERATOR_NAME EQ name.c_str()
			{
				rc = true;
			}
			END_FOR
			break;
		}
	}

	return rc;
}

static bool checkFieldExist(thread_db* tdbb, jrd_tra* transaction, const MetaName& relation, const MetaName& field)
{
	bool rc = false;

	AutoCacheRequest request(tdbb, drq_rel_field_exist, DYN_REQUESTS);
	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$RELATION_FIELDS
		WITH X.RDB$RELATION_NAME EQ relation.c_str() AND
			 X.RDB$FIELD_NAME EQ field.c_str()
	{
		rc = true;
	}
	END_FOR

	return rc;
}

// Execute SQL grant/revoke operation.
void GrantRevokeNode::grantRevoke(thread_db* tdbb, jrd_tra* transaction, const GranteeClause* object,
	const GranteeClause* userNod, const char* privs,
	MetaName field, int options)
{
	SSHORT userType = userNod->first;
	MetaName user(userNod->second);
	MetaName dummyName;
	const SSHORT objType = object ? object->first : obj_type_MAX;
	const MetaName objName(object ? object->second : "");
	bool crdb = false;

	AutoPtr<char, ArrayDelete> privileges(new char[MAX(strlen(ALL_PRIVILEGES), strlen(privs ? privs : "")) + 1]);
	strcpy(privileges, privs ? privs : "");

	if (strcmp(privileges, "A") == 0)
		strcpy(privileges, ALL_PRIVILEGES);

	char* cPtr = strchr(privileges, 'C');

	if (objType == obj_database && cPtr)
	{
		if (options || grantor)
		{
			(Arg::Gds(isc_wish_list) << Arg::Gds(isc_random) <<
			 "GRANT/ADMIN OPTION and GRANTED BY not supported for CREATE DATABASE grants").raise();
		}

		if (userType != obj_sql_role && userType != obj_user)
		{
			(Arg::Gds(isc_wish_list) << Arg::Gds(isc_random) <<
				 "Only grants to USER or ROLE are supported for CREATE DATABASE").raise();
		}

		crdb = true;
		size_t len = strlen(cPtr);
		memmove(cPtr, cPtr + 1, len);
	}

	// Check if grant object exists
	switch (userType)
	{
		case obj_user_or_role:
			// This test may become obsolete as we now allow explicit ROLE keyword.
			if (isItSqlRole(tdbb, transaction, user, dummyName))
			{
				userType = obj_sql_role;
				if (user == NULL_ROLE)
				{
					// msg 195: keyword NONE could not be used as SQL role name.
					status_exception::raise(Arg::PrivateDyn(195) << user.c_str());
				}
			}
			else
				userType = obj_user;
			break;

		case obj_user:
			// We may grant privilege to non existing user
			break;

		case obj_udf:
			if (!checkObjectExist(tdbb, transaction, user, userType))
				status_exception::raise(Arg::PrivateDyn(301) << user.c_str());	// Function @1 does not exist
			break;

		case obj_procedure:
			if (!checkObjectExist(tdbb, transaction, user, userType))
				status_exception::raise(Arg::PrivateDyn(302) << user.c_str());	// Procedure @1 does not exist
			break;

		case obj_package_header:
			if (!checkObjectExist(tdbb, transaction, user, userType))
				status_exception::raise(Arg::PrivateDyn(303) << user.c_str());	// Package @1 does not exist
			break;

		case obj_trigger:
			if (!checkObjectExist(tdbb, transaction, user, userType))
				status_exception::raise(Arg::PrivateDyn(304) << user.c_str());	// Trigger @1 does not exist
			break;

		case obj_view:
			if (!checkObjectExist(tdbb, transaction, user, userType))
				status_exception::raise(Arg::PrivateDyn(305) << user.c_str());	// View @1 does not exist
			break;

		case obj_sql_role:
			if (!crdb && (!isItSqlRole(tdbb, transaction, user, dummyName)))
			{
				// msg 188: Role doesn't exist.
				status_exception::raise(Arg::PrivateDyn(188) << user.c_str());
			}
			if (user == NULL_ROLE)
			{
				// msg 195: keyword NONE could not be used as SQL role name.
				status_exception::raise(Arg::PrivateDyn(195) << user.c_str());
			}
			break;

		case obj_privilege:		// Should convert symbolic privilege name to bit number
			{
				USHORT p = convertPrivilegeFromString(tdbb, transaction, user);
				user.printf("%d", p);
			}
			break;

	}

	// Check if grant subject exists
	switch (objType)
	{
		case obj_view:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(305) << objName.c_str());	// View @1 does not exist
			break;

		case obj_relation:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(306) << objName.c_str());	// Table @1 does not exist

			if (field.hasData() && !checkFieldExist(tdbb, transaction, objName, field))
				status_exception::raise(Arg::PrivateDyn(309) << field.c_str() << objName.c_str());	// Field @1 of table @2 does not exist
			break;

		case obj_trigger:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(304) << objName.c_str());	// Trigger @1 does not exist
			break;

		case obj_procedure:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(302) << objName.c_str());	// Procedure @1 does not exist
			break;

		case obj_exception:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(307) << objName.c_str());	// Exception @1 does not exist
			break;

		case obj_generator:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(308) << objName.c_str());	// Generator/Sequence @1 does not exist
			break;

		case obj_udf:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(301) << objName.c_str());	// Function @1 does not exist
			break;

		case obj_package_header:
			if (!checkObjectExist(tdbb, transaction, objName, objType))
				status_exception::raise(Arg::PrivateDyn(303) << objName.c_str());	// Package @1 does not exist
			break;

		case obj_sql_role:
			if (!isItSqlRole(tdbb, transaction, objName, dummyName))
				status_exception::raise(Arg::PrivateDyn(188) << objName.c_str());	// Role doesn't exist.
			break;

		default:
			fb_assert(object == NULL || objType >= obj_database);
	}

	if (options == 1)	// with grant option
	{
		switch (userType)
		{
			case obj_procedure:
				ERRD_post(Arg::Gds(isc_dsql_cant_grant_option) << Arg::Str("procedures"));
				break;

			case obj_trigger:
				ERRD_post(Arg::Gds(isc_dsql_cant_grant_option) << Arg::Str("triggers"));
				break;

			case obj_view:
				ERRD_post(Arg::Gds(isc_dsql_cant_grant_option) << Arg::Str("views"));
				break;

			case obj_privilege:
				ERRD_post(Arg::Gds(isc_dsql_cant_grant_option) << Arg::Str("system privileges"));
				break;

			case obj_udf:
				ERRD_post(Arg::Gds(isc_dsql_cant_grant_option) << Arg::Str("functions"));
				break;

			case obj_package_header:
				ERRD_post(Arg::Gds(isc_dsql_cant_grant_option) << Arg::Str("packages"));
				break;

			default:
				break;
		}
	}

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

	if (grantor && !attachment->locksmith(tdbb, USE_GRANTED_BY_CLAUSE))
	{
		const MetaName& owner(tdbb->getDatabase()->dbb_owner);

		if (owner == DBA_USER_NAME)
			(Arg::PrivateDyn(252) << DBA_USER_NAME).raise();
		else
			(Arg::PrivateDyn(295) << DBA_USER_NAME << owner).raise();
	}

	const MetaName currentUser(attachment->getEffectiveUserName());
	const MetaName grantorRevoker(grantor ? *grantor : currentUser);

	if (!isGrant && !privs)	// REVOKE ALL ON ALL
	{
		AutoCacheRequest request(tdbb, drq_e_grant3, DYN_REQUESTS);
		CreateDbJob all(userType, user);
		all.allOnAll = true;
		all.revoker = grantorRevoker;

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			PRIV IN RDB$USER_PRIVILEGES
			WITH PRIV.RDB$USER = user.c_str() AND
				 PRIV.RDB$USER_TYPE = userType AND
				 PRIV.RDB$GRANTOR NOT MISSING
		{
			if (attachment->att_user->locksmith(tdbb, GRANT_REVOKE_ON_ANY_OBJECT) ||
				grantorRevoker == PRIV.RDB$GRANTOR)
			{
				ERASE PRIV;
				all.grantErased = true;
			}
			else
				all.badGrantor = true;
		}
		END_FOR

		createDbJobs.push(all);

		return;
	}

	if (objType == obj_sql_role && objName == NULL_ROLE)
	{
		if (isGrant)
		{
			// msg 195: keyword NONE could not be used as SQL role name.
			status_exception::raise(Arg::PrivateDyn(195) << objName.c_str());
		}
		else
		{
			///CVC: Make this a warning in the future.
			///DYN_error_punt(false, 195, objName.c_str());
		}
	}

	if (crdb)
	{
		CreateDbJob job(userType, user);
		createDbJobs.push(job);

		if (!privileges[0])
			return;
	}

	char priv[2];
	priv[1] = '\0';

	if (isGrant)
	{
		AutoCacheRequest request(tdbb, drq_l_grant1, DYN_REQUESTS);

		for (const char* pr = privileges; *pr; ++pr)
		{
			bool duplicate = false;
			MetaName newField = field;
			int newOptions = options;
			priv[0] = *pr;

			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				PRIV IN RDB$USER_PRIVILEGES
				WITH PRIV.RDB$RELATION_NAME EQ objName.c_str() AND
					 PRIV.RDB$OBJECT_TYPE = objType AND
					 PRIV.RDB$PRIVILEGE EQ priv AND
					 PRIV.RDB$USER = user.c_str() AND
					 PRIV.RDB$USER_TYPE = userType AND
					 PRIV.RDB$GRANTOR EQ grantorRevoker.c_str() AND
					 (PRIV.RDB$FIELD_NAME EQUIV NULLIF(field.c_str(), '') OR
					  (PRIV.RDB$OBJECT_TYPE EQ obj_sql_role))
			{
				// It means we have such privilege without grant option but
				// user grants it with grant option. We should re-grant existing privilege.
				const bool addGrantOption = (!PRIV.RDB$GRANT_OPTION.NULL && !PRIV.RDB$GRANT_OPTION &&
					PRIV.RDB$GRANT_OPTION != options);

				// It means we have such granted role but without DEFAULT but user grants with DEFAULT.
				// We should re-grant it.
				const bool addDefaultRole = (objType == obj_sql_role && field == "D" && PRIV.RDB$FIELD_NAME.NULL);

				if (addGrantOption && !addDefaultRole) // Save DEFAULT option for re-grant
						newField = PRIV.RDB$FIELD_NAME;

				if (addDefaultRole && !addGrantOption) // Add grant option was requested
						newOptions = PRIV.RDB$GRANT_OPTION;

				duplicate = !addGrantOption && !addDefaultRole;

				if (!duplicate)
					ERASE PRIV;
			}
			END_FOR

			if (duplicate)
				continue;

			if (objType == obj_sql_role)
			{
				checkGrantorCanGrantRole(tdbb, transaction, grantorRevoker, objName);

				if (userType == obj_sql_role)
				{
					// Check for blocking cycles of role grants.
					UserId grantedRoles;
					grantedRoles.setSqlRole(objName);

					if (grantedRoles.roleInUse(tdbb, user))
					{
						// 292: role @1 can not be granted to role @2
						status_exception::raise(Arg::PrivateDyn(292) << objName.c_str() << user.c_str());
					}
				}
			}
			else
			{
				// In the case where the object is a view, then the grantor must have
				// some kind of grant privileges on the base table(s)/view(s).  If the
				// grantor is the owner of the view, then we have to explicitely check
				// this because the owner of a view by default has grant privileges on
				// his own view.  If the grantor is not the owner of the view, then the
				// base table/view grant privilege checks were made when the grantor
				// got its grant privilege on the view and no further checks are
				// necessary.
				// As long as only locksmith can use GRANTED BY, no need specially checking
				// for privileges of current user. AP-2008

				switch (objType)
				{
					case obj_relation:
					{
						// Relation or view because we cannot distinguish at this point.
						checkGrantorCanGrantRelation(tdbb, transaction, currentUser.c_str(), priv, objName,
							field, true);
						break;
					}

					case obj_procedure:
					case obj_udf:
					case obj_exception:
					case obj_generator:
					case obj_package_header:
					{
						checkGrantorCanGrantObject(tdbb, transaction, currentUser.c_str(), priv, objName, objType);
						break;
					}

					default:
						if (objType >= obj_database)
						{
							checkGrantorCanGrantDdl(tdbb, transaction, currentUser.c_str(), priv, objName);
						}
						else
							fb_assert(false);
				}
			}

			storePrivilege(tdbb, transaction, objName, user, newField, pr, userType, objType,
				newOptions, grantorRevoker);
		}
	}
	else	// REVOKE
	{
		const bool revokeDefaultRole = (objType == obj_sql_role) && field.hasData();
		const bool revokeGrantOption = options;

		// This var must be identical for request (1) and if below (2)
		const bool withField = field.hasData() && objType != obj_sql_role;

		AutoCacheRequest request(tdbb, (withField ? drq_e_grant1 : drq_e_grant2), DYN_REQUESTS); // (1)

		for (const char* pr = privileges; (priv[0] = *pr); ++pr)
		{
			bool grantErased = false;
			bool badGrantor = false;

			if (withField)	// (2)
			{
				FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
					PRIV IN RDB$USER_PRIVILEGES
					WITH PRIV.RDB$RELATION_NAME EQ objName.c_str() AND
						 PRIV.RDB$OBJECT_TYPE = objType AND
						 PRIV.RDB$PRIVILEGE EQ priv AND
						 PRIV.RDB$USER = user.c_str() AND
						 PRIV.RDB$USER_TYPE = userType AND
						 PRIV.RDB$FIELD_NAME EQ field.c_str() AND
						 PRIV.RDB$GRANTOR NOT MISSING
				{
					if (grantorRevoker == PRIV.RDB$GRANTOR)
					{
						MetaName newField = NULL;
						int newOptions = 0;
						if (!revokeGrantOption && !PRIV.RDB$GRANT_OPTION.NULL)
							newOptions = PRIV.RDB$GRANT_OPTION;
						if (!revokeDefaultRole && !PRIV.RDB$FIELD_NAME.NULL)
							newField = PRIV.RDB$FIELD_NAME;

						ERASE PRIV;
						grantErased = true;

						if (revokeDefaultRole || revokeGrantOption)
						{
							storePrivilege(tdbb, transaction, objName, user, newField, pr, userType, objType,
								newOptions, grantorRevoker);
						}
					}
					else
						badGrantor = true;
				}
				END_FOR
			}
			else
			{
				FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
					PRIV IN RDB$USER_PRIVILEGES
					WITH PRIV.RDB$PRIVILEGE EQ priv AND
						 PRIV.RDB$RELATION_NAME EQ objName.c_str() AND
						 PRIV.RDB$OBJECT_TYPE = objType AND
						 PRIV.RDB$USER EQ user.c_str() AND
						 PRIV.RDB$USER_TYPE = userType AND
						 PRIV.RDB$GRANTOR NOT MISSING
				{
					// Revoking a permission at the table level implies revoking the perm. on all
					// columns. So for all fields in this table which have been granted the
					// privilege, we erase the entries from RDB$USER_PRIVILEGES.

					MetaName owner;
					if ((grantorRevoker == PRIV.RDB$GRANTOR) ||
						(attachment->locksmith(tdbb, GRANT_REVOKE_ON_ANY_OBJECT) ||	// God-like check
						((objType == obj_sql_role) && (PRIV.RDB$PRIVILEGE[0] == 'M') &&		// This is ROLE to USER grant
						 (currentUser != user) &&						// And current user does not revoke his own grant
						 (isItSqlRole(tdbb, transaction, objName, owner) &&		// Pick up role owner name
						    (owner == currentUser)) ||							// Current user is role owner
						  (getGrantorOption(tdbb, transaction, currentUser, obj_user, objName) == 2))))	// or has ADMIN option
					{
						MetaName newField = NULL;
						int newOptions = 0;
						if (!revokeGrantOption && !PRIV.RDB$GRANT_OPTION.NULL)
							newOptions = PRIV.RDB$GRANT_OPTION;
						if (!revokeDefaultRole && !PRIV.RDB$FIELD_NAME.NULL)
							newField = PRIV.RDB$FIELD_NAME;

						ERASE PRIV;
						grantErased = true;

						if (revokeDefaultRole || revokeGrantOption)
						{
							storePrivilege(tdbb, transaction, objName, user, newField, pr, userType, objType,
								newOptions, grantorRevoker);
						}
					}
					else
						badGrantor = true;
				}
				END_FOR
			}

			if (!grantErased)
			{
				if (badGrantor)
				{
					// msg 246: @1 is not grantor of @2 on @3 to @4.
					status_exception::raise(Arg::PrivateDyn(246) <<
						grantorRevoker.c_str() << privilegeName(priv[0]) << objName.c_str() <<
						user.c_str());
				}

				// msg 247: Warning: @1 on @2 is not granted to @3.
				ERR_post_warning(
					Arg::Warning(isc_dyn_miss_priv_warning) <<
					Arg::Str(privilegeName(priv[0])) << Arg::Str(objName) << Arg::Str(user));
			}
		}
	}
}

// Check if the grantor has grant privilege on the relation/field.
void GrantRevokeNode::checkGrantorCanGrantRelation(thread_db* tdbb, jrd_tra* transaction,
	const char* grantor, const char* privilege, const MetaName& relationName,
	const MetaName& fieldName, bool topLevel)
{
	const Attachment* attachment = tdbb->getAttachment();

	// Verify that the input relation exists.

	AutoCacheRequest request(tdbb, drq_gcg4, DYN_REQUESTS);

	bool sqlRelation = false;
	bool relationExists = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		REL IN RDB$RELATIONS WITH
			REL.RDB$RELATION_NAME = relationName.c_str()
	{
		relationExists = true;
		if (!REL.RDB$FLAGS.NULL && (REL.RDB$FLAGS & REL_sql))
			sqlRelation = true;
	}
	END_FOR

	if (!relationExists)
	{
		// table/view .. does not exist
		status_exception::raise(Arg::PrivateDyn(175) << relationName.c_str());
	}

	// Verify the the input field exists.

	if (fieldName.hasData())
	{
		bool fieldExists = false;

		request.reset(tdbb, drq_gcg5, DYN_REQUESTS);

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			G_FLD IN RDB$RELATION_FIELDS WITH
				G_FLD.RDB$RELATION_NAME = relationName.c_str() AND
				G_FLD.RDB$FIELD_NAME = fieldName.c_str()
		{
			fieldExists = true;
		}
		END_FOR

		if (!fieldExists)
		{
			// column .. does not exist in table/view ..
			status_exception::raise(Arg::PrivateDyn(176) <<
				fieldName.c_str() << relationName.c_str());
		}
	}

	// If the current user is locksmith - allow all grants to occur

	if (attachment->locksmith(tdbb, GRANT_REVOKE_ON_ANY_OBJECT))
		return;

	// If this is a non-sql table, then the owner will probably not have any
	// entries in the rdb$user_privileges table.  Give the owner of a GDML
	// table all privileges.
	bool grantorIsOwner = false;

	request.reset(tdbb, drq_gcg2, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		REL IN RDB$RELATIONS WITH
			REL.RDB$RELATION_NAME = relationName.c_str() AND
			REL.RDB$OWNER_NAME = UPPERCASE(grantor)
	{
		grantorIsOwner = true;
	}
	END_FOR

	if (!sqlRelation && grantorIsOwner)
		return;

	// Remember the grant option for non field-specific user-privileges, and
	// the grant option for the user-privileges for the input field.
	// -1 = no privilege found (yet)
	// 0 = privilege without grant option found
	// 1 = privilege with grant option found
	SSHORT goRel = -1;
	SSHORT goFld = -1;

	// Verify that the grantor has the grant option for this relation/field
	// in the rdb$user_privileges.  If not, then we don't need to look further.

	request.reset(tdbb, drq_gcg1, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRV IN RDB$USER_PRIVILEGES WITH
			((PRV.RDB$USER = UPPERCASE(grantor) AND
				PRV.RDB$USER_TYPE = obj_user) OR (PRV.RDB$USER_TYPE = obj_sql_role)) AND
			PRV.RDB$RELATION_NAME = relationName.c_str() AND
			PRV.RDB$OBJECT_TYPE = obj_relation AND
			PRV.RDB$PRIVILEGE = privilege
	{
		if ( (PRV.RDB$USER_TYPE == obj_sql_role) && !attachment->att_user->roleInUse(tdbb, PRV.RDB$USER))
			continue;

		const bool hasGrantOption = !PRV.RDB$GRANT_OPTION.NULL && PRV.RDB$GRANT_OPTION;
		if (PRV.RDB$FIELD_NAME.NULL)
		{
			if (goRel == -1)
				goRel = hasGrantOption ? WITH_GRANT_OPTION : 0;
			else if ((goRel == 0) && hasGrantOption)
				goRel = WITH_GRANT_OPTION;
		}
		else
		{
			if (fieldName.hasData() && fieldName == PRV.RDB$FIELD_NAME)
			{
				if (goFld == -1)
					goFld = hasGrantOption ? WITH_GRANT_OPTION : 0;
				else if ((goFld == 0) && hasGrantOption)
					goFld = WITH_GRANT_OPTION;
			}
		}
		if ( (goRel > 0) && (goFld > 0 || !fieldName.hasData()) )
			break;	// We've found a privilege with grant option and can break the loop
	}
	END_FOR

	if (fieldName.hasData())
	{
		if (goFld == 0)
		{
			// no grant option for privilege .. on column .. of [base] table/view ..
			status_exception::raise(Arg::PrivateDyn(topLevel ? 167 : 168) <<
				privilegeName(*privilege) << fieldName.c_str() << relationName.c_str());
		}

		if (goFld == -1)
		{
			if (goRel == 0)
			{
				// no grant option for privilege .. on [base] table/view .. (for column ..)
				status_exception::raise(Arg::PrivateDyn(topLevel ? 169 : 170) <<
					privilegeName(*privilege) << relationName.c_str() << fieldName.c_str());
			}

			if (goRel == -1)
			{
				// no .. privilege with grant option on [base] table/view .. (for column ..)
				status_exception::raise(Arg::PrivateDyn(topLevel ? 171 : 172) <<
					privilegeName(*privilege) << relationName.c_str() << fieldName.c_str());
			}
		}
	}
	else
	{
		if (goRel == 0)
		{
			// no grant option for privilege .. on table/view ..
			status_exception::raise(Arg::PrivateDyn(173) << privilegeName(*privilege) << relationName.c_str());
		}

		if (goRel == -1)
		{
			// no .. privilege with grant option on table/view ..
			status_exception::raise(Arg::PrivateDyn(174) << privilegeName(*privilege) << relationName.c_str());
		}
	}

	// If the grantor is not the owner of the relation, then we don't need to
	// check the base table(s)/view(s) because that check was performed when
	// the grantor was given its privileges.

	if (!grantorIsOwner)
		return;

	// Find all the base fields/relations and check for the correct grant privileges on them.

	request.reset(tdbb, drq_gcg3, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		G_FLD IN RDB$RELATION_FIELDS CROSS
			G_VIEW IN RDB$VIEW_RELATIONS WITH
			G_FLD.RDB$RELATION_NAME = relationName.c_str() AND
			G_FLD.RDB$BASE_FIELD NOT MISSING AND
			G_VIEW.RDB$VIEW_NAME EQ G_FLD.RDB$RELATION_NAME AND
			G_VIEW.RDB$VIEW_CONTEXT EQ G_FLD.RDB$VIEW_CONTEXT
	{
		if (fieldName.hasData())
		{
			if (fieldName == G_FLD.RDB$FIELD_NAME)
			{
				checkGrantorCanGrantRelation(tdbb, transaction, grantor, privilege,
					G_VIEW.RDB$RELATION_NAME, G_FLD.RDB$BASE_FIELD, false);
			}
		}
		else
		{
			checkGrantorCanGrantRelation(tdbb, transaction, grantor, privilege,
				G_VIEW.RDB$RELATION_NAME, G_FLD.RDB$BASE_FIELD, false);
		}
	}
	END_FOR
}

/*
 * Function takes a role and grantor name and go through all roles granted to specified one.
 * If found target role with admin option returns 2, without admin option - 1.
 * Otherwise returns 0;
 *
 */
static int getGrantorOption(thread_db* tdbb, jrd_tra* transaction, const MetaName& grantor,
	int grantorType, const MetaName& roleName)
{
	AutoCacheRequest request(tdbb, drq_get_role_au, DYN_REQUESTS);

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRV IN RDB$USER_PRIVILEGES WITH
			PRV.RDB$USER = UPPERCASE(grantor.c_str()) AND
			PRV.RDB$USER_TYPE = grantorType AND
			PRV.RDB$OBJECT_TYPE = obj_sql_role AND
			PRV.RDB$PRIVILEGE EQ "M"
	{
		const MetaName role = PRV.RDB$RELATION_NAME;
		const bool grantable = PRV.RDB$GRANT_OPTION == WITH_ADMIN_OPTION;

		if (role == roleName)
			return grantable ? 2 : 1;
		else
		{
			switch (getGrantorOption(tdbb, transaction, role, obj_sql_role, roleName))
			{
			case 0:
				continue;
			case 1: // call found roleName we should stop searching
				return 1;
			case 2: // call found roleName with admin option but have we admin option of intermediate roles?
				return grantable ? 2 : 1;
			}
		}
	}
	END_FOR

	// we and calls did not found granted roleName and have to return 0
	return 0;
}

/*
 * Check if the grantor has admin privilege on the role or admin privilege on another role
 * which has admin privilege on it, etc
 * If recursive call of getGrantorOption returns:
 * 0 - role does not grant to grantor.
 * 1 - found but without admin option.
 * 2 - with admin option.
*/
void GrantRevokeNode::checkGrantorCanGrantRole(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& grantor, const MetaName& roleName)
{
	// Fetch the name of the owner of the ROLE.
	MetaName owner;
	if (isItSqlRole(tdbb, transaction, roleName, owner))
	{
		// Both SYSDBA and the owner of this ROLE can grant membership
		if (tdbb->getAttachment()->locksmith(tdbb, GRANT_REVOKE_ON_ANY_OBJECT) || owner == grantor)
			return;
	}
	else
	{
		// 188: role name not exist.
		status_exception::raise(Arg::PrivateDyn(188) << roleName.c_str());
	}

	const int r = getGrantorOption(tdbb, transaction, grantor, obj_user, roleName);

	if (r < 2)
	{
		// 189: user have no admin option.
		// 190: user is not a member of the role.
		status_exception::raise(Arg::PrivateDyn(r ? 189 : 190) << grantor.c_str() << roleName.c_str());
	}
}

// Check if the grantor has grant option on DDL privilege
void GrantRevokeNode::checkGrantorCanGrantDdl(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& grantor, const char* privilege, const MetaName& objName)
{
	const Attachment* attachment = tdbb->getAttachment();

	if (attachment->locksmith(tdbb, GRANT_REVOKE_ANY_DDL_RIGHT))
		return;

	AutoCacheRequest request(tdbb, drq_l_grant_option, DYN_REQUESTS);
	bool grantable = false;

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRV IN RDB$USER_PRIVILEGES
		WITH ((PRV.RDB$USER = UPPERCASE(grantor.c_str()) AND
				PRV.RDB$USER_TYPE = obj_user) OR (PRV.RDB$USER_TYPE = obj_sql_role)) AND
			 PRV.RDB$RELATION_NAME EQ objName.c_str() AND
			 PRV.RDB$OBJECT_TYPE >= obj_database AND
			 PRV.RDB$PRIVILEGE EQ privilege
	{
		if ( (PRV.RDB$USER_TYPE == obj_sql_role) && !attachment->att_user->roleInUse(tdbb, PRV.RDB$USER))
			continue;

		if (PRV.RDB$GRANT_OPTION == WITH_GRANT_OPTION)
		{
			grantable = true;
			break;	// We've found a privilege with grant option and can break the loop
		}
	}
	END_FOR

	if (!grantable)
	{
		// no @1 privilege with grant option on DDL @2
		status_exception::raise(Arg::PrivateDyn(299) << privilegeName(*privilege) << objName.c_str());
	}
}


// Check if the grantor has grant option on generator privilege
void GrantRevokeNode::checkGrantorCanGrantObject(thread_db* tdbb, jrd_tra* transaction, const char* grantor,
		const char* privilege, const MetaName& objName, SSHORT objType)
{
	const Attachment* attachment = tdbb->getAttachment();

	if (attachment->locksmith(tdbb, GRANT_REVOKE_ON_ANY_OBJECT))
		return;

	AutoCacheRequest request(tdbb, drq_l_grant_object, DYN_REQUESTS);
	bool grantable = false;

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRV IN RDB$USER_PRIVILEGES
		WITH ((PRV.RDB$USER = UPPERCASE(grantor) AND
				PRV.RDB$USER_TYPE = obj_user) OR (PRV.RDB$USER_TYPE = obj_sql_role)) AND
			 PRV.RDB$RELATION_NAME EQ objName.c_str() AND
			 PRV.RDB$OBJECT_TYPE = objType AND
			 PRV.RDB$PRIVILEGE EQ privilege
	{
		if ( (PRV.RDB$USER_TYPE == obj_sql_role) && !attachment->att_user->roleInUse(tdbb, PRV.RDB$USER))
			continue;

		if (PRV.RDB$GRANT_OPTION == WITH_GRANT_OPTION)
		{
			grantable = true;
			break;	// We've found a privilege with grant option and can break the loop
		}
	}
	END_FOR

	if (!grantable)
	{
		// no @1 privilege with grant option on object @2
		status_exception::raise(Arg::PrivateDyn(300) << privilegeName(*privilege) << objName.c_str());
	}
}


void GrantRevokeNode::storePrivilege(thread_db* tdbb, jrd_tra* transaction, const MetaName& object,
	const MetaName& user, const MetaName& field, const TEXT* privilege, SSHORT userType,
	SSHORT objType, int option, const MetaName& grantor)
{
	AutoCacheRequest request(tdbb, drq_s_grant, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		PRIV IN RDB$USER_PRIVILEGES
		PRIV.RDB$FIELD_NAME.NULL = TRUE;
		strcpy(PRIV.RDB$RELATION_NAME, object.c_str());
		strcpy(PRIV.RDB$USER, user.c_str());
		strcpy(PRIV.RDB$GRANTOR, grantor.c_str());
		PRIV.RDB$USER_TYPE = userType;
		PRIV.RDB$OBJECT_TYPE = objType;
	{
		if (field.hasData())
		{
			strcpy(PRIV.RDB$FIELD_NAME, field.c_str());
			PRIV.RDB$FIELD_NAME.NULL = FALSE;
			setFieldClassName(tdbb, transaction, object, field);
		}
		PRIV.RDB$PRIVILEGE[0] = privilege[0];
		PRIV.RDB$PRIVILEGE[1] = 0;
		PRIV.RDB$GRANT_OPTION = option;
	}
	END_STORE
}

// For field level grants, be sure the field has a unique class name.
void GrantRevokeNode::setFieldClassName(thread_db* tdbb, jrd_tra* transaction,
	const MetaName& relation, const MetaName& field)
{
	AutoCacheRequest request(tdbb, drq_s_f_class, DYN_REQUESTS);

	bool unique = false;

	FOR (REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		RFR IN RDB$RELATION_FIELDS
		WITH RFR.RDB$FIELD_NAME = field.c_str() AND
			 RFR.RDB$RELATION_NAME = relation.c_str() AND
			 RFR.RDB$SECURITY_CLASS MISSING
	{
		MODIFY RFR
			while (!unique)
		    {
				sprintf(RFR.RDB$SECURITY_CLASS, "%s%" SQUADFORMAT, SQL_FLD_SECCLASS_PREFIX,
				DPM_gen_id(tdbb, MET_lookup_generator(tdbb, SQL_SECCLASS_GENERATOR), false, 1));

				unique = true;

				AutoCacheRequest request2(tdbb, drq_s_u_class, DYN_REQUESTS);
				FOR (REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
					RFR1 IN RDB$RELATION_FIELDS
					WITH RFR1.RDB$SECURITY_CLASS = RFR.RDB$SECURITY_CLASS
				{
					unique = false;
				}
				END_FOR
		    }

			RFR.RDB$SECURITY_CLASS.NULL = FALSE;
		END_MODIFY
	}
	END_FOR
}


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


string AlterDatabaseNode::internalPrint(NodePrinter& printer) const
{
	DdlNode::internalPrint(printer);

	NODE_PRINT(printer, create);
	NODE_PRINT(printer, createLength);
	NODE_PRINT(printer, linger);
	NODE_PRINT(printer, clauses);
	NODE_PRINT(printer, differenceFile);
	NODE_PRINT(printer, setDefaultCharSet);
	NODE_PRINT(printer, setDefaultCollation);
	NODE_PRINT(printer, files);
	NODE_PRINT(printer, cryptPlugin);
	NODE_PRINT(printer, keyName);

	return "AlterDatabaseNode";
}

void AlterDatabaseNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_database(tdbb, SCL_alter);
}

void AlterDatabaseNode::checkClauses(thread_db* tdbb)
{
	// Raises msg 298: Incompatible ALTER DATABASE clauses: '@1' and '@2'

	if (clauses & CLAUSE_END_BACKUP)
	{
		if (clauses & CLAUSE_BEGIN_BACKUP)
			(Arg::PrivateDyn(298) << Arg::Str("BEGIN BACKUP") << Arg::Str("END BACKUP")).raise();

		if (differenceFile.hasData())
			(Arg::PrivateDyn(298) << Arg::Str("END BACKUP") << Arg::Str("ADD DIFFERENCE FILE")).raise();

		if (clauses & CLAUSE_DROP_DIFFERENCE)
			(Arg::PrivateDyn(298) << Arg::Str("END BACKUP") << Arg::Str("DROP DIFFERENCE FILE")).raise();
	}

	if ((clauses & CLAUSE_DROP_DIFFERENCE) && differenceFile.hasData())
		(Arg::PrivateDyn(298) << Arg::Str("ADD DIFFERENCE FILE") << Arg::Str("DROP DIFFERENCE FILE")).raise();

	if ((clauses & CLAUSE_ENABLE_PUB) && (clauses & CLAUSE_DISABLE_PUB))
		(Arg::PrivateDyn(298) << Arg::Str("ENABLE PUBLICATION") << Arg::Str("DISABLE PUBLICATION")).raise();

	if ((clauses & CLAUSE_PUB_INCL_TABLE) && (clauses & CLAUSE_PUB_EXCL_TABLE))
		(Arg::PrivateDyn(298) << Arg::Str("INCLUDE TABLE TO PUBLICATION") << Arg::Str("EXCLUDE TABLE FROM PUBLICATION")).raise();
}

void AlterDatabaseNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	checkClauses(tdbb);

	// Take a LCK_alter_database lock to prevent altering of the database from
	// parallel transactions

	if (!transaction->tra_alter_db_lock)
	{
		Lock* lock = FB_NEW_RPT(*transaction->tra_pool, 0) Lock(tdbb, 0, LCK_alter_database);
		lock->lck_data = transaction->tra_number;
		if (LCK_lock(tdbb, lock, LCK_write, transaction->getLockWait()))
			transaction->tra_alter_db_lock = lock;
		else
		{
			const TraNumber conflict_trans = LCK_read_data(tdbb, lock);
			delete lock;

			// msg 297: Concurrent ALTER DATABASE is not supported
			if (!conflict_trans)
				Arg::PrivateDyn(297).raise();

			// Cannot use Arg::Num here because transaction number is 64-bit unsigned integer
			(Arg::PrivateDyn(297) << Arg::Gds(isc_concurrent_transaction) << Arg::Int64(conflict_trans)).raise();
		}
	}

	// run all statements under savepoint control
	AutoSavePoint savePoint(tdbb, transaction);

	if (clauses & CLAUSE_DROP_DIFFERENCE)
		changeBackupMode(tdbb, transaction, CLAUSE_DROP_DIFFERENCE);

	if (clauses & (CLAUSE_ENABLE_PUB | CLAUSE_DISABLE_PUB))
	{
		AutoCacheRequest request(tdbb, drq_m_pub_state, DYN_REQUESTS);

		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			PUB IN RDB$PUBLICATIONS
			WITH PUB.RDB$PUBLICATION_NAME EQ DEFAULT_PUBLICATION
		{
			MODIFY PUB
				PUB.RDB$ACTIVE_FLAG.NULL = FALSE;
				PUB.RDB$ACTIVE_FLAG = (clauses & CLAUSE_ENABLE_PUB) ? 1 : 0;
			END_MODIFY
		}
		END_FOR
	}

	if (clauses & (CLAUSE_PUB_INCL_TABLE | CLAUSE_PUB_EXCL_TABLE))
	{
		if (pubTables.isEmpty())
		{
			AutoCacheRequest request(tdbb, drq_l_pub_all_rels, DYN_REQUESTS);

			FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
				REL IN RDB$RELATIONS
				WITH REL.RDB$SYSTEM_FLAG EQ 0
					AND REL.RDB$VIEW_BLR MISSING
			{
				const rel_t relType =
					relationType(REL.RDB$RELATION_TYPE.NULL, REL.RDB$RELATION_TYPE);

				if (relType == rel_persistent)
					pubTables.add(REL.RDB$RELATION_NAME);
			}
			END_FOR

			AutoCacheRequest request2(tdbb, drq_m_pub_mode, DYN_REQUESTS);

			FOR(REQUEST_HANDLE request2 TRANSACTION_HANDLE transaction)
				PUB IN RDB$PUBLICATIONS
				WITH PUB.RDB$PUBLICATION_NAME EQ DEFAULT_PUBLICATION
			{
				MODIFY PUB
					PUB.RDB$AUTO_ENABLE = (clauses & CLAUSE_PUB_INCL_TABLE) ? 1 : 0;
					PUB.RDB$AUTO_ENABLE.NULL = FALSE;
				END_MODIFY
			}
			END_FOR
		}
		else
		{
			AutoCacheRequest request(tdbb, drq_l_pub_rel_name, DYN_REQUESTS);

			for (const MetaName* iter = pubTables.begin(); iter != pubTables.end(); ++iter)
			{
				const MetaName& tableName = *iter;
				bool found = false;

				FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
					REL IN RDB$RELATIONS
					WITH REL.RDB$SYSTEM_FLAG EQ 0
						AND REL.RDB$VIEW_BLR MISSING
						AND REL.RDB$RELATION_NAME EQ tableName.c_str()
				{
					const rel_t relType =
						relationType(REL.RDB$RELATION_TYPE.NULL, REL.RDB$RELATION_TYPE);

					if (relType == rel_persistent)
						found = true;
				}
				END_FOR

				if (!found)
					status_exception::raise(Arg::Gds(isc_dyn_table_not_found) << tableName);
			}
		}

		for (const MetaName* iter = pubTables.begin(); iter != pubTables.end(); ++iter)
		{
			const MetaName& tableName = *iter;

			if (clauses & CLAUSE_PUB_INCL_TABLE)
			{
				try
				{
					RelationNode::addToPublication(tdbb, transaction,
												   tableName, DEFAULT_PUBLICATION);
				}
				catch (const status_exception& ex)
				{
					if (ex.value()[1] != isc_unique_key_violation)
						throw;

					// Ignore duplicated records
					fb_utils::init_status(tdbb->tdbb_status_vector);
				}
			}
			else
			{
				RelationNode::dropFromPublication(tdbb, transaction,
												  tableName, DEFAULT_PUBLICATION);
			}
		}
	}

	SLONG dbAlloc = PageSpace::maxAlloc(tdbb->getDatabase());
	SLONG start = create ? createLength + 1 : 0;
	for (NestConst<DbFileClause>* i = files.begin(); i != files.end(); ++i)
	{
		DbFileClause* file = *i;

		start = MAX(start, file->start);
		defineFile(tdbb, transaction, 0, false, false, dbAlloc,
			file->name.c_str(), start, file->length);
		start += file->length;
	}

	if (differenceFile.hasData())
		defineDifference(tdbb, transaction, differenceFile.c_str());

	if (clauses & CLAUSE_BEGIN_BACKUP)
		changeBackupMode(tdbb, transaction, CLAUSE_BEGIN_BACKUP);

	if (clauses & CLAUSE_END_BACKUP)
		changeBackupMode(tdbb, transaction, CLAUSE_END_BACKUP);

	if (setDefaultCharSet.hasData() || setDefaultCollation.hasData() || linger >= 0 ||
		ssDefiner.specified)
	{
		AutoCacheRequest request(tdbb, drq_m_database, DYN_REQUESTS);
		FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			DBB IN RDB$DATABASE
		{
			MODIFY DBB USING

			if (setDefaultCharSet.hasData())
			{
				if (!METD_get_charset(transaction, setDefaultCharSet.length(),
						setDefaultCharSet.c_str()))
				{
					// specified character set not found
					status_exception::raise(Arg::Gds(isc_charset_not_found) << setDefaultCharSet);
				}

				DBB.RDB$CHARACTER_SET_NAME.NULL = FALSE;
				strcpy(DBB.RDB$CHARACTER_SET_NAME, setDefaultCharSet.c_str());

				dsql_dbb* dbb = transaction->getDsqlAttachment();
				dbb->dbb_dfl_charset = "";	// reset in the cache
			}

			if (!DBB.RDB$CHARACTER_SET_NAME.NULL && setDefaultCollation.hasData())
			{
				AlterCharSetNode alterCharSetNode(dsqlScratch->getPool(), setDefaultCharSet, setDefaultCollation);
				alterCharSetNode.execute(tdbb, dsqlScratch, transaction);
			}

			if (linger >= 0)
			{
				DBB.RDB$LINGER.NULL = FALSE;
				DBB.RDB$LINGER = linger;
			}

			if (ssDefiner.specified)
			{
				DBB.RDB$SQL_SECURITY.NULL = FALSE;
				DBB.RDB$SQL_SECURITY = ssDefiner.value ? FB_TRUE : FB_FALSE;
			}

			END_MODIFY
		}
		END_FOR
	}

	// Load crypt plugin if it (by a miracle) wasn't already loaded yet
	if (clauses & CLAUSE_CRYPT)
	{
		Database* const db = tdbb->getDatabase();
		db->dbb_crypto_manager->prepareChangeCryptState(tdbb, cryptPlugin, keyName);

		DFW_post_work(transaction, dfw_db_crypt, cryptPlugin.c_str(), 0);
	}

	savePoint.release();	// everything is ok
}

// Drop backup difference file for the database, begin or end backup.
void AlterDatabaseNode::changeBackupMode(thread_db* tdbb, jrd_tra* transaction, unsigned clause)
{
	AutoCacheRequest request(tdbb, drq_d_difference, DYN_REQUESTS);
	bool invalidState = false;
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		X IN RDB$FILES
	{
		if (X.RDB$FILE_FLAGS & FILE_difference)
		{
			found = true;

			switch (clause)
			{
				case CLAUSE_DROP_DIFFERENCE:
					ERASE X;
					break;

				case CLAUSE_BEGIN_BACKUP:
					if (X.RDB$FILE_FLAGS & FILE_backing_up)
						invalidState = true;
					else
					{
						MODIFY X USING
							X.RDB$FILE_FLAGS |= FILE_backing_up;
						END_MODIFY
					}
					break;

				case CLAUSE_END_BACKUP:
					if (X.RDB$FILE_FLAGS & FILE_backing_up)
					{
						if (X.RDB$FILE_NAME.NULL)
							ERASE X;
						else
						{
							MODIFY X USING
								X.RDB$FILE_FLAGS &= ~FILE_backing_up;
							END_MODIFY
						}
					}
					else
						invalidState = true;
					break;
			}
		}
	}
	END_FOR

	if (!found && clause == CLAUSE_BEGIN_BACKUP)
	{
		request.reset(tdbb, drq_s2_difference, DYN_REQUESTS);

		STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
			X IN RDB$FILES
		{
			X.RDB$FILE_FLAGS = FILE_difference | FILE_backing_up;
			X.RDB$FILE_START = 0;
		}
		END_STORE

		found = true;
	}

	if (invalidState)
	{
		// msg 217: "Database is already in the physical backup mode"
		// msg 218: "Database is not in the physical backup mode"
		status_exception::raise(Arg::PrivateDyn(clause == CLAUSE_BEGIN_BACKUP ? 217 : 218));
	}

	if (!found)
	{
		// msg 218: "Database is not in the physical backup mode"
		// msg 215: "Difference file is not defined"
		status_exception::raise(Arg::PrivateDyn(clause == CLAUSE_END_BACKUP ? 218 : 215));
	}
}

// Define backup difference file.
void AlterDatabaseNode::defineDifference(thread_db* tdbb, jrd_tra* transaction, const PathName& file)
{
	AutoCacheRequest request(tdbb, drq_l_difference, DYN_REQUESTS);
	bool found = false;

	FOR(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIL IN RDB$FILES
	{
		if (FIL.RDB$FILE_FLAGS & FILE_difference)
			found = true;
	}
	END_FOR

	if (found)
	{
		// msg 216: "Difference file is already defined"
		status_exception::raise(Arg::PrivateDyn(216));
	}

	request.reset(tdbb, drq_s_difference, DYN_REQUESTS);

	STORE(REQUEST_HANDLE request TRANSACTION_HANDLE transaction)
		FIL IN RDB$FILES
	{
		if (file.length() >= sizeof(FIL.RDB$FILE_NAME))
			status_exception::raise(Arg::Gds(isc_dyn_name_longer));

		strcpy(FIL.RDB$FILE_NAME, file.c_str());
		FIL.RDB$FILE_FLAGS = FILE_difference;
		FIL.RDB$FILE_START = 0;
	}
	END_STORE
}


}	// namespace Jrd
