/*
 *  The contents of this file are subject to the Initial
 *  Developer's 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.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl.
 *
 *  Software distributed under the License is distributed AS IS,
 *  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 Adriano dos Santos Fernandes
 *  for the Firebird Open Source RDBMS project.
 *
 *  Copyright (c) 2009 Adriano dos Santos Fernandes <adrianosf@uol.com.br>
 *  and all contributors signed below.
 *
 *  All Rights Reserved.
 *  Contributor(s): ______________________________________.
 */

#include "firebird.h"
#include "../dsql/PackageNodes.h"
#include "../jrd/dyn.h"
#include "../jrd/intl.h"
#include "../jrd/jrd.h"
#include "../jrd/tra.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/exe_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/vio_proto.h"
#include "../dsql/make_proto.h"
#include "../dsql/pass1_proto.h"
#include "../common/StatusArg.h"
#include "../jrd/Attachment.h"
#include "../jrd/scl_proto.h"


using namespace Firebird;

namespace Jrd {

using namespace Firebird;

DATABASE DB = STATIC "ODS.RDB";


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


namespace
{
	struct ParameterInfo
	{
		explicit ParameterInfo(MemoryPool& p)
			: type(0),
			  number(0),
			  name(p),
			  fieldSource(p),
			  fieldName(p),
			  relationName(p),
			  mechanism(0)
		{
			defaultSource.clear();
			defaultValue.clear();
		}

		ParameterInfo(MemoryPool& p, const ParameterInfo& o)
			: type(o.type),
			  number(o.number),
			  name(p, o.name),
			  fieldSource(p, o.fieldSource),
			  fieldName(p, o.fieldName),
			  relationName(p, o.relationName),
			  collationId(o.collationId),
			  nullFlag(o.nullFlag),
			  mechanism(o.mechanism),
			  fieldLength(o.fieldLength),
			  fieldScale(o.fieldScale),
			  fieldType(o.fieldType),
			  fieldSubType(o.fieldSubType),
			  fieldSegmentLength(o.fieldSegmentLength),
			  fieldNullFlag(o.fieldNullFlag),
			  fieldCharLength(o.fieldCharLength),
			  fieldCollationId(o.fieldCollationId),
			  fieldCharSetId(o.fieldCharSetId),
			  fieldPrecision(o.fieldPrecision),
			  defaultSource(o.defaultSource),
			  defaultValue(o.defaultValue)
		{
		}

		SSHORT type;
		SSHORT number;
		MetaName name;
		MetaName fieldSource;
		MetaName fieldName;
		MetaName relationName;
		Nullable<SSHORT> collationId;
		Nullable<SSHORT> nullFlag;
		SSHORT mechanism;
		Nullable<SSHORT> fieldLength;
		Nullable<SSHORT> fieldScale;
		Nullable<SSHORT> fieldType;
		Nullable<SSHORT> fieldSubType;
		Nullable<SSHORT> fieldSegmentLength;
		Nullable<SSHORT> fieldNullFlag;
		Nullable<SSHORT> fieldCharLength;
		Nullable<SSHORT> fieldCollationId;
		Nullable<SSHORT> fieldCharSetId;
		Nullable<SSHORT> fieldPrecision;

		// Not compared
		bid defaultSource;
		bid defaultValue;

		bool operator >(const ParameterInfo& o) const
		{
			return type > o.type || (type == o.type && number > o.number);
		}

		bool operator ==(const ParameterInfo& o) const
		{
			return type == o.type &&
				number == o.number &&
				name == o.name &&
				(fieldSource == o.fieldSource ||
					(fb_utils::implicit_domain(fieldSource.c_str()) &&
						fb_utils::implicit_domain(o.fieldSource.c_str()))) &&
				fieldName == o.fieldName &&
				relationName == o.relationName &&
				collationId == o.collationId &&
				nullFlag.orElse(FALSE) == o.nullFlag.orElse(FALSE) &&
				mechanism == o.mechanism &&
				fieldLength == o.fieldLength &&
				fieldScale == o.fieldScale &&
				fieldType == o.fieldType &&
				fieldSubType.orElse(0) == o.fieldSubType.orElse(0) &&
				fieldSegmentLength == o.fieldSegmentLength &&
				fieldNullFlag.orElse(FALSE) == o.fieldNullFlag.orElse(FALSE) &&
				fieldCharLength == o.fieldCharLength &&
				fieldCollationId.orElse(0) == o.fieldCollationId.orElse(0) &&
				fieldCharSetId == o.fieldCharSetId &&
				fieldPrecision == o.fieldPrecision;
		}

		bool operator !=(const ParameterInfo& o) const
		{
			return !(*this == o);
		}
	};

	struct Signature
	{
		const static unsigned FLAG_DETERMINISTIC = 0x01;

		Signature(MemoryPool& p, const MetaName& aName)
			: name(p, aName),
			  parameters(p),
			  flags(0),
			  defined(false)
		{
		}

		explicit Signature(const MetaName& aName)
			: name(aName),
			  parameters(*getDefaultMemoryPool()),
			  flags(0),
			  defined(false)
		{
		}

		explicit Signature(MemoryPool& p)
			: name(p),
			  parameters(p),
			  flags(0),
			  defined(false)
		{
		}

		Signature(MemoryPool& p, const Signature& o)
			: name(p, o.name),
			  parameters(p),
			  flags(o.flags),
			  defined(o.defined)
		{
			for (SortedObjectsArray<ParameterInfo>::const_iterator i = o.parameters.begin();
				 i != o.parameters.end(); ++i)
			{
				parameters.add(*i);
			}
		}

		bool operator >(const Signature& o) const
		{
			return name > o.name;
		}

		bool operator ==(const Signature& o) const
		{
			if (name != o.name || flags != o.flags || parameters.getCount() != o.parameters.getCount())
				return false;

			for (SortedObjectsArray<ParameterInfo>::const_iterator i = parameters.begin(),
																   j = o.parameters.begin();
				i != parameters.end(); ++i, ++j)
			{
				if (*i != *j)
					return false;
			}

			return true;
		}

		bool operator !=(const Signature& o) const
		{
			return !(*this == o);
		}

		MetaName name;
		SortedObjectsArray<ParameterInfo> parameters;
		unsigned flags;
		bool defined;
	};


	// Return function and procedure names (in the user charset) and optionally its details for a
	// given package.
	void collectPackagedItems(thread_db* tdbb, jrd_tra* transaction, const MetaName& metaName,
		SortedObjectsArray<Signature>& functions,
		SortedObjectsArray<Signature>& procedures, bool details)
	{
		AutoCacheRequest requestHandle(tdbb, drq_l_pkg_funcs, DYN_REQUESTS);
		AutoCacheRequest requestHandle2(tdbb, drq_l_pkg_func_args, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			FUN IN RDB$FUNCTIONS
			WITH FUN.RDB$PACKAGE_NAME EQ metaName.c_str()
		{
			Signature function(FUN.RDB$FUNCTION_NAME);
			function.defined = !FUN.RDB$FUNCTION_BLR.NULL || !FUN.RDB$ENTRYPOINT.NULL;

			if (!FUN.RDB$DETERMINISTIC_FLAG.NULL && FUN.RDB$DETERMINISTIC_FLAG != 0)
				function.flags |= Signature::FLAG_DETERMINISTIC;

			if (details)
			{
				FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
					ARG IN RDB$FUNCTION_ARGUMENTS CROSS
					FLD IN RDB$FIELDS
					WITH ARG.RDB$PACKAGE_NAME EQ metaName.c_str() AND
						 ARG.RDB$FUNCTION_NAME EQ FUN.RDB$FUNCTION_NAME AND
						 FLD.RDB$FIELD_NAME EQ ARG.RDB$FIELD_SOURCE
				{
					ParameterInfo parameter(*getDefaultMemoryPool());

					parameter.number = ARG.RDB$ARGUMENT_POSITION;
					parameter.name = ARG.RDB$ARGUMENT_NAME;
					parameter.fieldSource = ARG.RDB$FIELD_SOURCE;
					parameter.mechanism = ARG.RDB$ARGUMENT_MECHANISM;

					if (!ARG.RDB$FIELD_NAME.NULL)
						parameter.fieldName = ARG.RDB$FIELD_NAME;
					if (!ARG.RDB$RELATION_NAME.NULL)
						parameter.relationName = ARG.RDB$RELATION_NAME;
					if (!ARG.RDB$COLLATION_ID.NULL)
						parameter.collationId = ARG.RDB$COLLATION_ID;
					if (!ARG.RDB$NULL_FLAG.NULL)
						parameter.nullFlag = ARG.RDB$NULL_FLAG;

					if (!FLD.RDB$FIELD_LENGTH.NULL)
						parameter.fieldLength = FLD.RDB$FIELD_LENGTH;
					if (!FLD.RDB$FIELD_SCALE.NULL)
						parameter.fieldScale = FLD.RDB$FIELD_SCALE;
					if (!FLD.RDB$FIELD_TYPE.NULL)
						parameter.fieldType = FLD.RDB$FIELD_TYPE;
					if (!FLD.RDB$FIELD_SUB_TYPE.NULL)
						parameter.fieldSubType = FLD.RDB$FIELD_SUB_TYPE;
					if (!FLD.RDB$SEGMENT_LENGTH.NULL)
						parameter.fieldSegmentLength = FLD.RDB$SEGMENT_LENGTH;
					if (!FLD.RDB$NULL_FLAG.NULL)
						parameter.fieldNullFlag = FLD.RDB$NULL_FLAG;
					if (!FLD.RDB$CHARACTER_LENGTH.NULL)
						parameter.fieldCharLength = FLD.RDB$CHARACTER_LENGTH;
					if (!FLD.RDB$COLLATION_ID.NULL)
						parameter.fieldCollationId = FLD.RDB$COLLATION_ID;
					if (!FLD.RDB$CHARACTER_SET_ID.NULL)
						parameter.fieldCharSetId = FLD.RDB$CHARACTER_SET_ID;
					if (!FLD.RDB$FIELD_PRECISION.NULL)
						parameter.fieldPrecision = FLD.RDB$FIELD_PRECISION;

					if (!ARG.RDB$DEFAULT_SOURCE.NULL)
						parameter.defaultSource = ARG.RDB$DEFAULT_SOURCE;
					if (!ARG.RDB$DEFAULT_VALUE.NULL)
						parameter.defaultValue = ARG.RDB$DEFAULT_VALUE;

					function.parameters.add(parameter);
				}
				END_FOR
			}

			functions.add(function);
		}
		END_FOR

		requestHandle.reset(tdbb, drq_l_pkg_procs, DYN_REQUESTS);
		requestHandle2.reset(tdbb, drq_l_pkg_proc_args, DYN_REQUESTS);

		FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
			PRC IN RDB$PROCEDURES
			WITH PRC.RDB$PACKAGE_NAME EQ metaName.c_str()
		{
			Signature procedure(PRC.RDB$PROCEDURE_NAME);
			procedure.defined = !PRC.RDB$PROCEDURE_BLR.NULL || !PRC.RDB$ENTRYPOINT.NULL;

			if (details)
			{
				FOR (REQUEST_HANDLE requestHandle2 TRANSACTION_HANDLE transaction)
					PRM IN RDB$PROCEDURE_PARAMETERS CROSS
					FLD IN RDB$FIELDS
					WITH PRM.RDB$PACKAGE_NAME EQ metaName.c_str() AND
						 PRM.RDB$PROCEDURE_NAME EQ PRC.RDB$PROCEDURE_NAME AND
						 FLD.RDB$FIELD_NAME EQ PRM.RDB$FIELD_SOURCE
				{
					ParameterInfo parameter(*getDefaultMemoryPool());
					parameter.type = PRM.RDB$PARAMETER_TYPE;
					parameter.number = PRM.RDB$PARAMETER_NUMBER;
					parameter.name = PRM.RDB$PARAMETER_NAME;
					parameter.fieldSource = PRM.RDB$FIELD_SOURCE;
					parameter.mechanism = PRM.RDB$PARAMETER_MECHANISM;

					if (!PRM.RDB$FIELD_NAME.NULL)
						parameter.fieldName = PRM.RDB$FIELD_NAME;
					if (!PRM.RDB$RELATION_NAME.NULL)
						parameter.relationName = PRM.RDB$RELATION_NAME;
					if (!PRM.RDB$COLLATION_ID.NULL)
						parameter.collationId = PRM.RDB$COLLATION_ID;
					if (!PRM.RDB$NULL_FLAG.NULL)
						parameter.nullFlag = PRM.RDB$NULL_FLAG;

					if (!FLD.RDB$FIELD_LENGTH.NULL)
						parameter.fieldLength = FLD.RDB$FIELD_LENGTH;
					if (!FLD.RDB$FIELD_SCALE.NULL)
						parameter.fieldScale = FLD.RDB$FIELD_SCALE;
					if (!FLD.RDB$FIELD_TYPE.NULL)
						parameter.fieldType = FLD.RDB$FIELD_TYPE;
					if (!FLD.RDB$FIELD_SUB_TYPE.NULL)
						parameter.fieldSubType = FLD.RDB$FIELD_SUB_TYPE;
					if (!FLD.RDB$SEGMENT_LENGTH.NULL)
						parameter.fieldSegmentLength = FLD.RDB$SEGMENT_LENGTH;
					if (!FLD.RDB$NULL_FLAG.NULL)
						parameter.fieldNullFlag = FLD.RDB$NULL_FLAG;
					if (!FLD.RDB$CHARACTER_LENGTH.NULL)
						parameter.fieldCharLength = FLD.RDB$CHARACTER_LENGTH;
					if (!FLD.RDB$COLLATION_ID.NULL)
						parameter.fieldCollationId = FLD.RDB$COLLATION_ID;
					if (!FLD.RDB$CHARACTER_SET_ID.NULL)
						parameter.fieldCharSetId = FLD.RDB$CHARACTER_SET_ID;
					if (!FLD.RDB$FIELD_PRECISION.NULL)
						parameter.fieldPrecision = FLD.RDB$FIELD_PRECISION;

					if (!PRM.RDB$DEFAULT_SOURCE.NULL)
						parameter.defaultSource = PRM.RDB$DEFAULT_SOURCE;
					if (!PRM.RDB$DEFAULT_VALUE.NULL)
						parameter.defaultValue = PRM.RDB$DEFAULT_VALUE;

					procedure.parameters.add(parameter);
				}
				END_FOR
			}

			procedures.add(procedure);
		}
		END_FOR
	}
}	// namespace


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


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

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, create);
	NODE_PRINT(printer, alter);
	NODE_PRINT(printer, source);
	//// FIXME-PRINT: NODE_PRINT(printer, items);
	NODE_PRINT(printer, functionNames);
	NODE_PRINT(printer, procedureNames);

	return "CreateAlterPackageNode";
}


DdlNode* CreateAlterPackageNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	MemoryPool& pool = dsqlScratch->getPool();

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

	// items
	for (unsigned i = 0; i < items->getCount(); ++i)
	{
		DsqlCompiledStatement* itemStatement = FB_NEW_POOL(pool) DsqlCompiledStatement(pool);

		DsqlCompilerScratch* itemScratch = (*items)[i].dsqlScratch =
			FB_NEW_POOL(pool) DsqlCompilerScratch(pool, dsqlScratch->getAttachment(),
				dsqlScratch->getTransaction(), itemStatement);

		itemScratch->clientDialect = dsqlScratch->clientDialect;
		itemScratch->flags |= DsqlCompilerScratch::FLAG_DDL;
		itemScratch->package = name;

		if (itemScratch->clientDialect > SQL_DIALECT_V5)
			itemStatement->setBlrVersion(5);
		else
			itemStatement->setBlrVersion(4);

		switch ((*items)[i].type)
		{
			case CreateAlterPackageNode::Item::FUNCTION:
			{
				CreateAlterFunctionNode* const fun = (*items)[i].function;

				if (functionNames.exist(fun->name))
				{
					status_exception::raise(
						Arg::Gds(isc_no_meta_update) <<
						Arg::Gds(isc_dyn_duplicate_package_item) <<
							Arg::Str("FUNCTION") << Arg::Str(fun->name));
				}

				functionNames.add(fun->name);

				fun->alter = true;
				fun->package = name;
				fun->dsqlPass(itemScratch);
				break;
			}

			case CreateAlterPackageNode::Item::PROCEDURE:
			{
				CreateAlterProcedureNode* const proc = (*items)[i].procedure;

				if (procedureNames.exist(proc->name))
				{
					status_exception::raise(
						Arg::Gds(isc_no_meta_update) <<
						Arg::Gds(isc_dyn_duplicate_package_item) <<
							Arg::Str("PROCEDURE") << Arg::Str(proc->name));
				}

				procedureNames.add(proc->name);

				proc->alter = true;
				proc->package = name;
				proc->dsqlPass(itemScratch);
				break;
			}
		}
	}

	return DdlNode::dsqlPass(dsqlScratch);
}


bool CreateAlterPackageNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());

	if (alter)
	{
		if (SCL_check_package(tdbb, &dscName, SCL_alter) || !create)
			return true;
	}

	SCL_check_create_access(tdbb, SCL_object_package);
	return true;
}


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

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

	//dbb->checkOdsForDsql(ODS_12_0);

	// 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
			{
				status_exception::raise(
					Arg::Gds(isc_no_meta_update) <<
					Arg::Gds(isc_dyn_package_not_found) << Arg::Str(name));
			}
		}
	}
	else
		executeCreate(tdbb, dsqlScratch, transaction);

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


void CreateAlterPackageNode::executeCreate(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	Attachment* attachment = transaction->getAttachment();
	const string& userName = attachment->att_user->usr_user_name;

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

	AutoCacheRequest requestHandle(tdbb, drq_s_pkg, DYN_REQUESTS);

	STORE (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PKG IN RDB$PACKAGES USING
	{
		PKG.RDB$PACKAGE_NAME.NULL = FALSE;
		strcpy(PKG.RDB$PACKAGE_NAME, name.c_str());

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

		PKG.RDB$OWNER_NAME.NULL = FALSE;
		strcpy(PKG.RDB$OWNER_NAME, userName.c_str());

		PKG.RDB$PACKAGE_HEADER_SOURCE.NULL = FALSE;
		attachment->storeMetaDataBlob(tdbb, transaction, &PKG.RDB$PACKAGE_HEADER_SOURCE, source);
	}
	END_STORE

	storePrivileges(tdbb, transaction, name, obj_package_header, EXEC_PRIVILEGES);

	owner = userName;

	executeItems(tdbb, dsqlScratch, transaction);

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


bool CreateAlterPackageNode::executeAlter(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	MemoryPool& pool = dsqlScratch->getPool();
	Attachment* attachment = transaction->getAttachment();
	AutoCacheRequest requestHandle(tdbb, drq_m_pkg, DYN_REQUESTS);
	bool modified = false;

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PKG IN RDB$PACKAGES
		WITH PKG.RDB$PACKAGE_NAME EQ name.c_str()
	{
		modified = true;

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

		SortedObjectsArray<Signature> existingFuncs(pool);
		SortedObjectsArray<Signature> existingProcs(pool);
		collectPackagedItems(tdbb, transaction, name, existingFuncs, existingProcs, false);

		for (SortedObjectsArray<Signature>::iterator i = existingFuncs.begin();
			 i != existingFuncs.end(); ++i)
		{
			if (!functionNames.exist(i->name))
			{
				DropFunctionNode dropNode(pool, i->name);
				dropNode.package = name;
				dropNode.dsqlPass(dsqlScratch);
				dropNode.executeDdl(tdbb, dsqlScratch, transaction);
			}
		}

		for (SortedObjectsArray<Signature>::iterator i = existingProcs.begin();
			 i != existingProcs.end(); ++i)
		{
			if (!procedureNames.exist(i->name))
			{
				DropProcedureNode dropNode(pool, i->name);
				dropNode.package = name;
				dropNode.dsqlPass(dsqlScratch);
				dropNode.executeDdl(tdbb, dsqlScratch, transaction);
			}
		}

		MODIFY PKG
			PKG.RDB$PACKAGE_HEADER_SOURCE.NULL = FALSE;
			attachment->storeMetaDataBlob(tdbb, transaction, &PKG.RDB$PACKAGE_HEADER_SOURCE,
				source);

			if (!PKG.RDB$VALID_BODY_FLAG.NULL)
				PKG.RDB$VALID_BODY_FLAG = FALSE;
		END_MODIFY

		owner = PKG.RDB$OWNER_NAME;

		dsc desc;
		desc.makeText(name.length(), ttype_metadata,
			(UCHAR*) const_cast<char*>(name.c_str()));	// safe const_cast
		DFW_post_work(transaction, dfw_drop_package_body, &desc, 0);
	}
	END_FOR

	if (modified)
	{
		executeItems(tdbb, dsqlScratch, transaction);

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

	return modified;
}


void CreateAlterPackageNode::executeItems(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	for (unsigned i = 0; i < items->getCount(); ++i)
	{
		switch ((*items)[i].type)
		{
			case Item::FUNCTION:
				(*items)[i].function->packageOwner = owner;
				(*items)[i].function->executeDdl(tdbb, (*items)[i].dsqlScratch, transaction);
				break;

			case Item::PROCEDURE:
				(*items)[i].procedure->packageOwner = owner;
				(*items)[i].procedure->executeDdl(tdbb, (*items)[i].dsqlScratch, transaction);
				break;
		}
	}
}


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


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

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

	return "DropPackageNode";
}


bool DropPackageNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	SCL_check_package(tdbb, &dscName, SCL_drop);
	return true;
}


void DropPackageNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	MemoryPool& pool = dsqlScratch->getPool();

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

	bool found = false;
	AutoCacheRequest requestHandle(tdbb, drq_e_pkg, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PKG IN RDB$PACKAGES
		WITH PKG.RDB$PACKAGE_NAME EQ name.c_str()
	{
		found = true;

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

		ERASE PKG;

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

		dsc desc;
		desc.makeText(name.length(), ttype_metadata,
			(UCHAR*) const_cast<char*>(name.c_str()));	// safe const_cast
		DFW_post_work(transaction, dfw_drop_package_header, &desc, 0);
	}
	END_FOR

	if (!found && !silent)
	{
		status_exception::raise(
			Arg::Gds(isc_no_meta_update) <<
			Arg::Gds(isc_dyn_package_not_found) << Arg::Str(name));
	}

	SortedObjectsArray<Signature> existingFuncs(pool);
	SortedObjectsArray<Signature> existingProcs(pool);
	collectPackagedItems(tdbb, transaction, name, existingFuncs, existingProcs, false);

	for (SortedObjectsArray<Signature>::iterator i = existingFuncs.begin();
		 i != existingFuncs.end(); ++i)
	{
		DropFunctionNode dropNode(pool, i->name);
		dropNode.package = name;
		dropNode.dsqlPass(dsqlScratch);
		dropNode.executeDdl(tdbb, dsqlScratch, transaction);
	}

	for (SortedObjectsArray<Signature>::iterator i = existingProcs.begin();
		 i != existingProcs.end(); ++i)
	{
		DropProcedureNode dropNode(pool, i->name);
		dropNode.package = name;
		dropNode.dsqlPass(dsqlScratch);
		dropNode.executeDdl(tdbb, dsqlScratch, transaction);
	}

	requestHandle.reset(tdbb, drq_e_pkg_prv, 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_package_header) OR
			 (PRIV.RDB$USER EQ name.c_str() AND PRIV.RDB$USER_TYPE = obj_package_header)) AND
			 PRIV.RDB$GRANTOR NOT MISSING
	{
		ERASE PRIV;
	}
	END_FOR

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

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


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


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

	NODE_PRINT(printer, name);
	NODE_PRINT(printer, source);
	//// FIXME-PRINT: NODE_PRINT(printer, declaredItems);
	//// FIXME-PRINT: NODE_PRINT(printer, items);

	return "CreatePackageBodyNode";
}


DdlNode* CreatePackageBodyNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
	MemoryPool& pool = dsqlScratch->getPool();

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

	// process declaredItems and items
	Array<CreateAlterPackageNode::Item>* arrays[] = {declaredItems, items};
	SortedArray<MetaName> functionNames[FB_NELEM(arrays)];
	SortedArray<MetaName> procedureNames[FB_NELEM(arrays)];

	for (unsigned i = 0; i < FB_NELEM(arrays); ++i)
	{
		if (!arrays[i])
			continue;

		for (unsigned j = 0; j < arrays[i]->getCount(); ++j)
		{
			DsqlCompiledStatement* itemStatement = FB_NEW_POOL(pool) DsqlCompiledStatement(pool);

			DsqlCompilerScratch* itemScratch = (*arrays[i])[j].dsqlScratch =
				FB_NEW_POOL(pool) DsqlCompilerScratch(pool, dsqlScratch->getAttachment(),
					dsqlScratch->getTransaction(), itemStatement);

			itemScratch->clientDialect = dsqlScratch->clientDialect;
			itemScratch->flags |= DsqlCompilerScratch::FLAG_DDL;
			itemScratch->package = name;

			if (itemScratch->clientDialect > SQL_DIALECT_V5)
				itemStatement->setBlrVersion(5);
			else
				itemStatement->setBlrVersion(4);

			switch ((*arrays[i])[j].type)
			{
				case CreateAlterPackageNode::Item::FUNCTION:
				{
					CreateAlterFunctionNode* const fun = (*arrays[i])[j].function;

					if (functionNames[i].exist(fun->name))
					{
						status_exception::raise(
							Arg::Gds(isc_no_meta_update) <<
							Arg::Gds(isc_dyn_duplicate_package_item) <<
								Arg::Str("FUNCTION") << Arg::Str(fun->name));
					}

					functionNames[i].add(fun->name);

					fun->package = name;
					fun->create = true;

					if (arrays[i] == items)
						fun->alter = true;

					fun->dsqlPass(itemScratch);

					break;
				}

				case CreateAlterPackageNode::Item::PROCEDURE:
				{
					CreateAlterProcedureNode* const proc = (*arrays[i])[j].procedure;

					if (procedureNames[i].exist(proc->name))
					{
						status_exception::raise(
							Arg::Gds(isc_no_meta_update) <<
							Arg::Gds(isc_dyn_duplicate_package_item) <<
								Arg::Str("PROCEDURE") << Arg::Str(proc->name));
					}

					procedureNames[i].add(proc->name);

					proc->package = name;
					proc->create = true;

					if (arrays[i] == items)
						proc->alter = true;

					proc->dsqlPass(itemScratch);

					break;
				}
			}
		}
	}

	return DdlNode::dsqlPass(dsqlScratch);
}


bool CreatePackageBodyNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	SCL_check_create_access(tdbb, SCL_object_package);
	return true;
}


void CreatePackageBodyNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	MemoryPool& pool = dsqlScratch->getPool();
	Attachment* attachment = transaction->getAttachment();

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

	AutoCacheRequest requestHandle(tdbb, drq_m_pkg_body, DYN_REQUESTS);
	bool modified = false;

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PKG IN RDB$PACKAGES
		WITH PKG.RDB$PACKAGE_NAME EQ name.c_str()
	{
		if (!PKG.RDB$VALID_BODY_FLAG.NULL && PKG.RDB$VALID_BODY_FLAG != 0)
		{
			status_exception::raise(
				Arg::Gds(isc_no_meta_update) <<
				Arg::Gds(isc_dyn_package_body_exists) << Arg::Str(name));
		}

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

		MODIFY PKG
			PKG.RDB$VALID_BODY_FLAG.NULL = FALSE;
			PKG.RDB$VALID_BODY_FLAG = TRUE;

			PKG.RDB$PACKAGE_BODY_SOURCE.NULL = FALSE;
			attachment->storeMetaDataBlob(tdbb, transaction, &PKG.RDB$PACKAGE_BODY_SOURCE, source);
		END_MODIFY

		modified = true;

		owner = PKG.RDB$OWNER_NAME;
	}
	END_FOR

	if (!modified)
	{
		status_exception::raise(
			Arg::Gds(isc_no_meta_update) <<
			Arg::Gds(isc_dyn_package_not_found) << Arg::Str(name));
	}

	SortedObjectsArray<Signature> headerFuncs(pool);
	SortedObjectsArray<Signature> headerProcs(pool);
	collectPackagedItems(tdbb, transaction, name, headerFuncs, headerProcs, false);

	SortedObjectsArray<Signature> existingFuncs(pool);
	SortedObjectsArray<Signature> existingProcs(pool);

	// process declaredItems and items
	Array<CreateAlterPackageNode::Item>* arrays[] = {declaredItems, items};

	for (unsigned i = 0; i < FB_NELEM(arrays); ++i)
	{
		if (!arrays[i])
			continue;

		if (arrays[i] == items)
		{
			existingFuncs.clear();
			existingProcs.clear();
		}

		collectPackagedItems(tdbb, transaction, name, existingFuncs, existingProcs, true);

		for (unsigned j = 0; j < arrays[i]->getCount(); ++j)
		{
			CreateAlterPackageNode::Item& elem = (*arrays[i])[j];

			switch (elem.type)
			{
				case CreateAlterPackageNode::Item::FUNCTION:
				{
					CreateAlterFunctionNode* func = elem.function;

					if (arrays[i] == items)
						func->privateScope = !headerFuncs.exist(Signature(func->name));
					else if (existingFuncs.exist(Signature(func->name)))
					{
						status_exception::raise(
							Arg::Gds(isc_no_meta_update) <<
							Arg::Gds(isc_dyn_duplicate_package_item) <<
								Arg::Str("FUNCTION") << Arg::Str(func->name));
					}

					func->packageOwner = owner;
					func->preserveDefaults =
						existingFuncs.exist(Signature(func->name)) && arrays[i] == items;
					func->executeDdl(tdbb, elem.dsqlScratch, transaction);
					break;
				}

				case CreateAlterPackageNode::Item::PROCEDURE:
				{
					CreateAlterProcedureNode* proc = elem.procedure;

					if (arrays[i] == items)
						proc->privateScope = !headerProcs.exist(Signature(proc->name));
					else if (existingProcs.exist(Signature(proc->name)))
					{
						status_exception::raise(
							Arg::Gds(isc_no_meta_update) <<
							Arg::Gds(isc_dyn_duplicate_package_item) <<
								Arg::Str("PROCEDURE") << Arg::Str(proc->name));
					}

					proc->packageOwner = owner;
					proc->preserveDefaults =
						existingProcs.exist(Signature(proc->name)) && arrays[i] == items;
					proc->executeDdl(tdbb, elem.dsqlScratch, transaction);
					break;
				}
			}
		}
	}

	SortedObjectsArray<Signature> newFuncs(pool);
	SortedObjectsArray<Signature> newProcs(pool);
	collectPackagedItems(tdbb, transaction, name, newFuncs, newProcs, true);

	for (SortedObjectsArray<Signature>::iterator i = existingFuncs.begin();
		 i != existingFuncs.end(); ++i)
	{
		FB_SIZE_T pos;
		bool found = newFuncs.find(Signature(pool, i->name), pos);

		if (!found || !newFuncs[pos].defined)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_funcnotdef_package) << i->name.c_str() << name.c_str());
		}
		else if (newFuncs[pos] != *i)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_funcsignat_package) << i->name.c_str() << name.c_str());
		}
	}

	for (SortedObjectsArray<Signature>::iterator i = existingProcs.begin();
		 i != existingProcs.end(); ++i)
	{
		FB_SIZE_T pos;
		bool found = newProcs.find(Signature(pool, i->name), pos);

		if (!found || !newProcs[pos].defined)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_procnotdef_package) << i->name.c_str() << name.c_str());
		}
		else if (newProcs[pos] != *i)
		{
			status_exception::raise(
				Arg::Gds(isc_dyn_procsignat_package) << i->name.c_str() << name.c_str());
		}
	}

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

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


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


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

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

	return "DropPackageBodyNode";
}


bool DropPackageBodyNode::checkPermission(thread_db* tdbb, jrd_tra* transaction)
{
	dsc dscName;
	dscName.makeText(name.length(), CS_METADATA, (UCHAR*) name.c_str());
	SCL_check_package(tdbb, &dscName, SCL_drop);
	return true;
}


void DropPackageBodyNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch,
	jrd_tra* transaction)
{
	MemoryPool& pool = dsqlScratch->getPool();

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

	bool found = false;
	AutoCacheRequest requestHandle(tdbb, drq_m_pkg_body2, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PKG IN RDB$PACKAGES
		WITH PKG.RDB$PACKAGE_NAME EQ name.c_str()
	{
		found = true;

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

		MODIFY PKG
			PKG.RDB$VALID_BODY_FLAG.NULL = TRUE;
			PKG.RDB$PACKAGE_BODY_SOURCE.NULL = TRUE;

			dsc desc;
			desc.makeText(name.length(), ttype_metadata,
				(UCHAR*) const_cast<char*>(name.c_str()));	// safe const_cast
			DFW_post_work(transaction, dfw_drop_package_body, &desc, 0);
		END_MODIFY
	}
	END_FOR

	if (!found)
	{
		status_exception::raise(
			Arg::Gds(isc_no_meta_update) <<
			Arg::Gds(isc_dyn_package_not_found) << Arg::Str(name));
	}

	requestHandle.reset(tdbb, drq_m_pkg_fun, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		FUN IN RDB$FUNCTIONS
		WITH FUN.RDB$PACKAGE_NAME EQ name.c_str()
	{
		if (!FUN.RDB$PRIVATE_FLAG.NULL && FUN.RDB$PRIVATE_FLAG != 0)
		{
			DropFunctionNode dropNode(pool, FUN.RDB$FUNCTION_NAME);
			dropNode.package = name;
			dropNode.dsqlPass(dsqlScratch);
			dropNode.executeDdl(tdbb, dsqlScratch, transaction);
		}
		else
		{
			MODIFY FUN
				FUN.RDB$FUNCTION_TYPE.NULL = TRUE;
				FUN.RDB$FUNCTION_BLR.NULL = TRUE;
				FUN.RDB$DEBUG_INFO.NULL = TRUE;
				FUN.RDB$MODULE_NAME.NULL = TRUE;
				FUN.RDB$ENGINE_NAME.NULL = TRUE;
				FUN.RDB$ENTRYPOINT.NULL = TRUE;
			END_MODIFY
		}
	}
	END_FOR

	requestHandle.reset(tdbb, drq_m_pkg_prc, DYN_REQUESTS);

	FOR (REQUEST_HANDLE requestHandle TRANSACTION_HANDLE transaction)
		PRC IN RDB$PROCEDURES
		WITH PRC.RDB$PACKAGE_NAME EQ name.c_str()
	{
		if (!PRC.RDB$PRIVATE_FLAG.NULL && PRC.RDB$PRIVATE_FLAG != 0)
		{
			DropProcedureNode dropNode(pool, PRC.RDB$PROCEDURE_NAME);
			dropNode.package = name;
			dropNode.dsqlPass(dsqlScratch);
			dropNode.executeDdl(tdbb, dsqlScratch, transaction);
		}
		else
		{
			MODIFY PRC
				PRC.RDB$PROCEDURE_TYPE.NULL = TRUE;
				PRC.RDB$PROCEDURE_BLR.NULL = TRUE;
				PRC.RDB$DEBUG_INFO.NULL = TRUE;
				PRC.RDB$ENGINE_NAME.NULL = TRUE;
				PRC.RDB$ENTRYPOINT.NULL = TRUE;
			END_MODIFY
		}
	}
	END_FOR

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

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


}	// namespace Jrd
