/*
 *	PROGRAM:	JRD Access Method
 *	MODULE:		ini.epp
 *	DESCRIPTION:	Metadata initialization / population
 *
 * 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): ______________________________________.
 */

#include "firebird.h"
#include <stdio.h>
#include <string.h>
#include "../jrd/flags.h"
#include "../jrd/jrd.h"
#include "../jrd/val.h"
#include "../jrd/ods.h"
#include "../jrd/btr.h"
#include "../jrd/ids.h"
#include "../jrd/intl.h"
#include "../jrd/tra.h"
#include "../jrd/trig.h"
#include "../jrd/intl.h"
#include "../jrd/dflt.h"
#include "../jrd/ini.h"
#include "../jrd/idx.h"
#include "../common/gdsassert.h"
#include "../dsql/dsql.h"
#include "../jrd/blb_proto.h"
#include "../jrd/cch_proto.h"
#include "../jrd/cmp_proto.h"
#include "../jrd/dfw_proto.h"
#include "../jrd/dpm_proto.h"
#include "../jrd/err_proto.h"
#include "../jrd/exe_proto.h"
#include "../yvalve/gds_proto.h"
#include "../jrd/idx_proto.h"
#include "../jrd/ini_proto.h"
#include "../jrd/jrd_proto.h"
#include "../jrd/met_proto.h"
#include "../jrd/obj.h"
#include "../jrd/acl.h"
#include "../jrd/dyn.h"
#include "../jrd/irq.h"
#include "../jrd/IntlManager.h"
#include "../jrd/PreparedStatement.h"
#include "../jrd/constants.h"
#include "../jrd/grant_proto.h"
#include "../jrd/SystemPackages.h"

using namespace Firebird;
using namespace Jrd;

DATABASE DB = FILENAME "ODS.RDB";

#define PAD(string, field) jrd_vtof ((char*)(string), field, sizeof (field))
const int FB_MAX_ACL_SIZE	= 4096;


static void add_index_set(thread_db*);
static void add_security_to_sys_obj(thread_db*, AutoRequest&, AutoRequest&, AutoRequest&,
	const MetaName&, USHORT, const MetaName&, USHORT = 0, const UCHAR* = NULL);
static void add_security_class(thread_db* tdbb, AutoRequest&, const MetaName& class_name,
	USHORT acl_length, const UCHAR* acl);
static void add_security_to_sys_rel(thread_db*, AutoRequest&, AutoRequest&, AutoRequest&, const MetaName&,
	const TEXT*, const USHORT, const UCHAR*);
static void store_default_pub(thread_db*, const MetaName&);
static void store_generator(thread_db*, const gen*, AutoRequest&, const MetaName&);
static void store_global_field(thread_db*, const gfld*, AutoRequest&, const MetaName&);
static void store_intlnames(thread_db*, const MetaName&);
static void store_message(thread_db*, const trigger_msg*, AutoRequest&);
static void store_relation_field(thread_db*, const int*, const int*, int, AutoRequest&);
static void store_packages(thread_db* tdbb, const MetaName& owner);
static void store_trigger(thread_db*, const jrd_trg*, AutoRequest&);
static void store_admin_grant(thread_db*, const char* grantee, USHORT grantee_type,
    const char* object, USHORT object_type, const char* prvl, USHORT option = 0, bool dflt = false);
static void store_admin_role_grant(thread_db*, const char*);
static void store_admin_role(thread_db*, const MetaName&, MetaName, UserId::Privileges*);
static bool get_charset_by_text_type(SSHORT& charSet, const USHORT sub_type);

// This is the table used in defining triggers; note that
// RDB$TRIGGER_0 was first changed to RDB$TRIGGER_7 to make it easier to
// upgrade a database to support field-level grant.  It has since been
// changed to RDB$TRIGGER_9 to handle SQL security on relations whose
// name is > 27 characters

static const jrd_trg triggers[] =
{
	{ "RDB$TRIGGER_1", (UCHAR) nam_user_privileges,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger3), trigger3,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_8", (UCHAR) nam_user_privileges,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger2), trigger2,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_9", (UCHAR) nam_user_privileges,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_STORE, sizeof(trigger1), trigger1,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_2", (UCHAR) nam_trgs,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger4), trigger4,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_3", (UCHAR) nam_trgs,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger4), trigger4,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_26", (UCHAR) nam_rel_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_STORE, sizeof(trigger26), trigger26,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_25", (UCHAR) nam_rel_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger25),
		trigger25, 0, ODS_8_0 },
	{ "RDB$TRIGGER_10", (UCHAR) nam_rel_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger10), trigger10,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_11", (UCHAR) nam_rel_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.POST_ERASE, sizeof(trigger11),
		trigger11, 0, ODS_8_0 },
	{ "RDB$TRIGGER_12", (UCHAR) nam_ref_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_STORE, sizeof(trigger12), trigger12,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_13", (UCHAR) nam_ref_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger13),
		trigger13, 0, ODS_8_0 },
	{ "RDB$TRIGGER_14", (UCHAR) nam_chk_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger14),
		trigger14, 0, ODS_8_0 },
	{ "RDB$TRIGGER_15", (UCHAR) nam_chk_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger15), trigger15,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_16", (UCHAR) nam_chk_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.POST_ERASE, sizeof(trigger16),
		trigger16, 0, ODS_8_0 },
	{ "RDB$TRIGGER_17", (UCHAR) nam_i_segments,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger17), trigger17,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_18", (UCHAR) nam_i_segments,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger18),
		trigger18, 0, ODS_8_0 },
	{ "RDB$TRIGGER_19", (UCHAR) nam_indices,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger19), trigger19,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_20", (UCHAR) nam_indices,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger20),
		trigger20, 0, ODS_8_0 },
	{ "RDB$TRIGGER_21", (UCHAR) nam_trgs,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger21), trigger21,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_22", (UCHAR) nam_trgs,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger22),
		trigger22, 0, ODS_8_0 },
	{ "RDB$TRIGGER_23", (UCHAR) nam_r_fields,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger23), trigger23,
		0, ODS_8_0 },
	{ "RDB$TRIGGER_24", (UCHAR) nam_r_fields,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger24),
		trigger24, 0, ODS_8_0 },
	{ "RDB$TRIGGER_27", (UCHAR) nam_r_fields,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.POST_ERASE, sizeof(trigger27),
		trigger27, 0, ODS_8_0 },
	{ "RDB$TRIGGER_31", (UCHAR) nam_user_privileges,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger31),
		trigger31, 0, ODS_8_1 },
	{ "RDB$TRIGGER_32", (UCHAR) nam_user_privileges,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_ERASE, sizeof(trigger31), trigger31,
		0, ODS_8_1 },
	{ "RDB$TRIGGER_33", (UCHAR) nam_user_privileges,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_STORE, sizeof(trigger31), trigger31,
		0, ODS_8_1 },
	{ "RDB$TRIGGER_34", (UCHAR) nam_rel_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.POST_ERASE, sizeof(trigger34),
		trigger34, TRG_ignore_perm, ODS_8_1 },
	{ "RDB$TRIGGER_35", (UCHAR) nam_chk_constr,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.POST_ERASE, sizeof(trigger35),
		trigger35, TRG_ignore_perm, ODS_8_1 },
	{ "RDB$TRIGGER_36", (UCHAR) nam_fields,
		RDB$TRIGGERS.RDB$TRIGGER_TYPE.PRE_MODIFY, sizeof(trigger36),
		trigger36, 0, ODS_11_0 },
	{ 0, 0, 0, 0, 0, 0 }
};


// this table is used in defining messages for system triggers

static const trigger_msg trigger_messages[] =
{
	{ "RDB$TRIGGER_9", 0, "grant_obj_notfound", ODS_8_0 },
	{ "RDB$TRIGGER_9", 1, "grant_fld_notfound", ODS_8_0 },
	{ "RDB$TRIGGER_9", 2, "grant_nopriv", ODS_8_0 },
	{ "RDB$TRIGGER_9", 3, "nonsql_security_rel", ODS_8_0 },
	{ "RDB$TRIGGER_9", 4, "nonsql_security_fld", ODS_8_0 },
	{ "RDB$TRIGGER_9", 5, "grant_nopriv_on_base", ODS_8_0 },
	{ "RDB$TRIGGER_1", 0, "existing_priv_mod", ODS_8_0 },
	{ "RDB$TRIGGER_2", 0, "systrig_update", ODS_8_0 },
	{ "RDB$TRIGGER_3", 0, "systrig_update", ODS_8_0 },
	{ "RDB$TRIGGER_24", 1, "cnstrnt_fld_rename", ODS_8_0 },
	{ "RDB$TRIGGER_23", 1, "cnstrnt_fld_del", ODS_8_0 },
	{ "RDB$TRIGGER_22", 1, "check_trig_update", ODS_8_0 },
	{ "RDB$TRIGGER_21", 1, "check_trig_del", ODS_8_0 },
	{ "RDB$TRIGGER_20", 1, "integ_index_mod", ODS_8_0 },
	{ "RDB$TRIGGER_20", 2, "integ_index_deactivate", ODS_8_0 },
	{ "RDB$TRIGGER_20", 3, "integ_deactivate_primary", ODS_8_0 },
	{ "RDB$TRIGGER_19", 1, "integ_index_del", ODS_8_0 },
	{ "RDB$TRIGGER_18", 1, "integ_index_seg_mod", ODS_8_0 },
	{ "RDB$TRIGGER_17", 1, "integ_index_seg_del", ODS_8_0 },
	{ "RDB$TRIGGER_15", 1, "check_cnstrnt_del", ODS_8_0 },
	{ "RDB$TRIGGER_14", 1, "check_cnstrnt_update", ODS_8_0 },
	{ "RDB$TRIGGER_13", 1, "ref_cnstrnt_update", ODS_8_0 },
	{ "RDB$TRIGGER_12", 1, "ref_cnstrnt_notfound", ODS_8_0 },
	{ "RDB$TRIGGER_12", 2, "foreign_key_notfound", ODS_8_0 },
	{ "RDB$TRIGGER_10", 1, "primary_key_ref", ODS_8_0 },
	{ "RDB$TRIGGER_10", 2, "primary_key_notnull", ODS_8_0 },
	{ "RDB$TRIGGER_25", 1, "rel_cnstrnt_update", ODS_8_0 },
	{ "RDB$TRIGGER_26", 1, "constaint_on_view", ODS_8_0 },
	{ "RDB$TRIGGER_26", 2, "invld_cnstrnt_type", ODS_8_0 },
	{ "RDB$TRIGGER_26", 3, "primary_key_exists", ODS_8_0 },
	{ "RDB$TRIGGER_31", 0, "no_write_user_priv", ODS_8_1 },
	{ "RDB$TRIGGER_32", 0, "no_write_user_priv", ODS_8_1 },
	{ "RDB$TRIGGER_33", 0, "no_write_user_priv", ODS_8_1 },
	{ "RDB$TRIGGER_24", 2, "integ_index_seg_mod", ODS_11_0 },
	{ "RDB$TRIGGER_36", 1, "integ_index_seg_mod", ODS_11_0 },
	{ 0, 0, 0, 0 }
};



void INI_format(const char* owner, const char* charset)
{
/**************************************
 *
 *	I N I _ f o r m a t
 *
 **************************************
 *
 * Functional description
 *	Initialize system relations in the database.
 *	The full complement of metadata should be
 *	stored here.
 *
 **************************************/
	thread_db* tdbb = JRD_get_thread_data();
	Jrd::Attachment* attachment = tdbb->getAttachment();
	Database* const dbb = tdbb->getDatabase();

	fb_assert(owner && owner[0]);

	// Uppercase owner name
	MetaName ownerName(owner);

	// Uppercase charset name
	MetaName rdbCharSetName(charset && charset[0] ? charset : DEFAULT_DB_CHARACTER_SET_NAME);

	const int* fld;

	// Make sure relations exist already

	for (const int* relfld = relfields; relfld[RFLD_R_NAME]; relfld = fld + 1)
	{
		if (relfld[RFLD_R_TYPE] == rel_persistent)
		{
			DPM_create_relation(tdbb, MET_relation(tdbb, relfld[RFLD_R_ID]));
		}

		for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH)
			;
	}

	jrd_tra* transaction = attachment->getSysTransaction();

	// Store RELATIONS and RELATION_FIELDS

	SLONG rdbRelationId;
	MetaName rdbRelationName;
	SLONG rdbFieldId;
	SLONG rdbSystemFlag = RDB_system;
	SLONG rdbRelationType;

	PreparedStatement::Builder sql;
	sql << "insert into rdb$relations (rdb$relation_id, rdb$relation_name, rdb$field_id,"
		<< "rdb$format, rdb$system_flag, rdb$dbkey_length, rdb$owner_name, rdb$relation_type)"
		<< "values (" << rdbRelationId << ", " << rdbRelationName << ", " << rdbFieldId << ", 0,"
		<< rdbSystemFlag << ", 8, " << ownerName << ", " << rdbRelationType << ")";

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

	AutoRequest handle1;
	for (const int* relfld = relfields; relfld[RFLD_R_NAME]; relfld = fld + 1)
	{
		for (rdbFieldId = 0, fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH)
		{
			const int* pFld = fld;
			const int* pRelFld = relfld;
			store_relation_field(tdbb, pFld, pRelFld, rdbFieldId, handle1);
			++rdbFieldId;
		}

		rdbRelationId = relfld[RFLD_R_ID];
		rdbRelationName = names[relfld[RFLD_R_NAME]];
		rdbRelationType = relfld[RFLD_R_TYPE];
		ps->execute(tdbb, transaction);
	}

	handle1.reset();

	// Store global FIELDS

	for (const gfld* gfield = gfields; gfield->gfld_name; gfield++)
		store_global_field(tdbb, gfield, handle1, ownerName);

	// Store DATABASE record

	sql = PreparedStatement::Builder();
	sql << "insert into rdb$database (rdb$relation_id, rdb$character_set_name) values ("
		<< rdbRelationId << ", " << rdbCharSetName << ")";

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

	rdbRelationId = USER_DEF_REL_INIT_ID;
	ps->execute(tdbb, transaction);

	// Store default publication

	store_default_pub(tdbb, ownerName);

	// Store system role

	store_admin_role(tdbb, ADMIN_ROLE, ownerName, NULL);

	// Store grants of admin role to valuable users

	store_admin_role_grant(tdbb, DBA_USER_NAME);
	if (ownerName != DBA_USER_NAME)
		store_admin_role_grant(tdbb, ownerName.c_str());

	// Create indices for system relations

	add_index_set(tdbb);

	// Create parameter types

	handle1.reset();

	for (const rtyp* type = types; type->rtyp_name; ++type)
	{
		// this STORE should be compatible with two below,
		// as they use the same compiled request
		STORE(REQUEST_HANDLE handle1) X IN RDB$TYPES
		{
			PAD(names[type->rtyp_field], X.RDB$FIELD_NAME);
			PAD(type->rtyp_name, X.RDB$TYPE_NAME);
			X.RDB$TYPE = type->rtyp_value;
			X.RDB$SYSTEM_FLAG = RDB_system;
			X.RDB$SYSTEM_FLAG.NULL = FALSE;
		}
		END_STORE
	}

	for (const IntlManager::CharSetDefinition* charSet = IntlManager::defaultCharSets;
		 charSet->name; ++charSet)
	{
		// this STORE should be compatible with one above and below,
		// as they use the same compiled request
		STORE(REQUEST_HANDLE handle1) X IN RDB$TYPES
			PAD(names[nam_charset_name], X.RDB$FIELD_NAME);
			PAD(charSet->name, X.RDB$TYPE_NAME);
			X.RDB$TYPE = charSet->id;
			X.RDB$SYSTEM_FLAG = RDB_system;
			X.RDB$SYSTEM_FLAG.NULL = FALSE;
		END_STORE;
	}

	for (const IntlManager::CharSetAliasDefinition* alias = IntlManager::defaultCharSetAliases;
		alias->name; ++alias)
	{
		// this STORE should be compatible with two above,
		// as they use the same compiled request
		STORE(REQUEST_HANDLE handle1) X IN RDB$TYPES
			PAD(names[nam_charset_name], X.RDB$FIELD_NAME);
			PAD(alias->name, X.RDB$TYPE_NAME);
			X.RDB$TYPE = alias->charSetId;
			X.RDB$SYSTEM_FLAG = RDB_system;
			X.RDB$SYSTEM_FLAG.NULL = FALSE;
		END_STORE;
	}

	// Store symbols for international character sets & collations
	store_intlnames(tdbb, ownerName);

	// Create system generators

	handle1.reset();

	for (const gen* generator = generators; generator->gen_name; generator++)
		store_generator(tdbb, generator, handle1, ownerName);

	// Adjust the value of the hidden generator RDB$GENERATORS
	DPM_gen_id(tdbb, 0, true, FB_NELEM(generators) - 1);

	store_packages(tdbb, ownerName);

	const size_t ownerNameLength = ownerName.length();
	fb_assert(ownerNameLength <= MAX_UCHAR);

	// Add security for the non-relation system metadata objects

	const UCHAR NON_REL_OWNER_ACL[] =
		{ACL_priv_list, priv_control, priv_alter, priv_drop, priv_usage, ACL_end};

	const UCHAR NON_REL_PUBLIC_USAGE_ACL[] =
		{ACL_priv_list, priv_usage, ACL_end};

	const UCHAR PKG_PUBLIC_EXECUTE_ACL[] =
		{ACL_priv_list, priv_execute, ACL_end};

	UCHAR buffer[FB_MAX_ACL_SIZE];
	UCHAR* acl = buffer;
	*acl++ = ACL_version;
	*acl++ = ACL_id_list;
	*acl++ = id_person;

	*acl++ = (UCHAR) ownerNameLength;
	memcpy(acl, ownerName.c_str(), ownerNameLength);
	acl += ownerNameLength;

	*acl++ = ACL_end;

	memcpy(acl, NON_REL_OWNER_ACL, sizeof(NON_REL_OWNER_ACL));
	acl += sizeof(NON_REL_OWNER_ACL);

	*acl++ = ACL_id_list;
	*acl++ = ACL_end;

	UCHAR* aclPublicStart = acl;

	memcpy(acl, NON_REL_PUBLIC_USAGE_ACL, sizeof(NON_REL_PUBLIC_USAGE_ACL));
	acl += sizeof(NON_REL_PUBLIC_USAGE_ACL);
	*acl++ = ACL_end; // Put an extra terminator to avoid scl.epp:walk_acl() missing the end.

	USHORT length = acl - buffer;

	AutoRequest reqAddSC;

	{ // scope
		AutoRequest reqModObjSC, reqInsUserPriv;

		for (const gfld* gfield = gfields; gfield->gfld_name; gfield++)
		{
			add_security_to_sys_obj(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName, obj_field,
				names[(USHORT)gfield->gfld_name], length, buffer);
		}

		reqModObjSC.reset();
		for (const gen* generator = generators; generator->gen_name; generator++)
		{
			add_security_to_sys_obj(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName, obj_generator,
				generator->gen_name, length, buffer);
		}

		reqModObjSC.reset();
		for (const IntlManager::CharSetDefinition* charset = IntlManager::defaultCharSets;
			charset->name;
			++charset)
		{
			add_security_to_sys_obj(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName, obj_charset,
				charset->name, length, buffer);
		}

		reqModObjSC.reset();
		for (const IntlManager::CollationDefinition* collation = IntlManager::defaultCollations;
			collation->name;
			++collation)
		{
			add_security_to_sys_obj(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName, obj_collation,
				collation->name, length, buffer);
		}

		reqModObjSC.reset();
		add_security_to_sys_obj(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName, obj_sql_role,
				ADMIN_ROLE, length, buffer);

		// Must be last!
		acl = aclPublicStart;
		memcpy(acl, PKG_PUBLIC_EXECUTE_ACL, sizeof(PKG_PUBLIC_EXECUTE_ACL));
		acl += sizeof(PKG_PUBLIC_EXECUTE_ACL);
		*acl++ = ACL_end; // Put an extra terminator to avoid scl.epp:walk_acl() missing the end.
		length = acl - buffer;

		reqModObjSC.reset();
		for (auto& systemPackage : SystemPackage::get())
		{
			if (systemPackage.odsVersion > ENCODE_ODS(dbb->dbb_ods_version, dbb->dbb_minor_version))
				continue;

			add_security_to_sys_obj(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName,
				obj_package_header, systemPackage.name, length, buffer);
		}
	}

	// Add default DDL security

	const UCHAR DDL_OWNER_ACL[] =
		{ACL_priv_list, priv_control, priv_alter, priv_drop, ACL_end};

	const UCHAR DDL_PUBLIC_ACL[] =
		{ACL_priv_list, ACL_end};

	acl = buffer;
	*acl++ = ACL_version;
	*acl++ = ACL_id_list;
	*acl++ = id_person;

	*acl++ = (UCHAR) ownerNameLength;
	memcpy(acl, ownerName.c_str(), ownerNameLength);
	acl += ownerNameLength;

	*acl++ = ACL_end;

	memcpy(acl, DDL_OWNER_ACL, sizeof(DDL_OWNER_ACL));
	acl += sizeof(DDL_OWNER_ACL);

	*acl++ = ACL_id_list;
	*acl++ = ACL_end;
	memcpy(acl, DDL_PUBLIC_ACL, sizeof(DDL_PUBLIC_ACL));
	acl += sizeof(DDL_PUBLIC_ACL);
	*acl++ = ACL_end; // Put an extra terminator to avoid scl.epp:walk_acl() missing the end.

	length = acl - buffer;

	for (int ddl_obj = obj_database + 1; ddl_obj < obj_type_MAX; ++ddl_obj)
	{
		add_security_class(tdbb, reqAddSC, get_object_name(ddl_obj), length, buffer);
	}

	{ // scope
		AutoRequest reqModObjSC, reqInsUserPriv;

		add_security_to_sys_obj(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName, obj_database,
			"", length, buffer);
	}
	// Add security on system tables

	const UCHAR REL_OWNER_ACL[] =
		{ACL_priv_list, priv_control, priv_alter, priv_drop,
		 priv_select, priv_insert, priv_update, priv_delete, ACL_end};

	const UCHAR REL_PUBLIC_ACL[] =
		{ACL_priv_list, priv_select, ACL_end};

	acl = buffer;
	*acl++ = ACL_version;
	*acl++ = ACL_id_list;
	*acl++ = id_person;

	*acl++ = (UCHAR) ownerNameLength;
	memcpy(acl, ownerName.c_str(), ownerNameLength);
	acl += ownerNameLength;

	*acl++ = ACL_end;

	memcpy(acl, REL_OWNER_ACL, sizeof(REL_OWNER_ACL));
	acl += sizeof(REL_OWNER_ACL);

	*acl++ = ACL_id_list;
	*acl++ = ACL_end;
	memcpy(acl, REL_PUBLIC_ACL, sizeof(REL_PUBLIC_ACL));
	acl += sizeof(REL_PUBLIC_ACL);
	*acl++ = ACL_end; // Put an extra terminator to avoid scl.epp:walk_acl() missing the end.

	length = acl - buffer;

	{ // scope
		AutoRequest reqModObjSC, reqInsUserPriv;
		for (const int* relfld = relfields; relfld[RFLD_R_NAME]; relfld = fld + 1)
		{
			for (rdbFieldId = 0, fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH)
				++rdbFieldId;

			add_security_to_sys_rel(tdbb, reqAddSC, reqModObjSC, reqInsUserPriv, ownerName, names[relfld[RFLD_R_NAME]], length, buffer);
		}
	}

	// store system-defined triggers

	handle1.reset();

	for (const jrd_trg* trigger = triggers; trigger->trg_relation; ++trigger)
		store_trigger(tdbb, trigger, handle1);

	// store trigger messages to go with triggers

	handle1.reset();

	for (const trigger_msg* message = trigger_messages; message->trigmsg_name; ++message)
		store_message(tdbb, message, handle1);

	handle1.reset();

	// Add additional grants

	MetaName buf;

	buf.printf("%d", USE_NBACKUP_UTILITY);
	store_admin_grant(tdbb, buf.c_str(), obj_privilege, "RDB$BACKUP_HISTORY", obj_relation, "SIUDR");
	GRANT_privileges(tdbb, "RDB$BACKUP_HISTORY", obj_relation, attachment->getSysTransaction());

	buf.printf("%d", CREATE_USER_TYPES);
	store_admin_grant(tdbb, buf.c_str(), obj_privilege, "RDB$TYPES", obj_relation, "SIUDR");
	GRANT_privileges(tdbb, "RDB$TYPES", obj_relation, attachment->getSysTransaction());

	buf.printf("%d", GRANT_REVOKE_ANY_DDL_RIGHT);
	store_admin_grant(tdbb, buf.c_str(), obj_privilege, "RDB$DB_CREATORS", obj_relation, "SIUDR");
	GRANT_privileges(tdbb, "RDB$DB_CREATORS", obj_relation, attachment->getSysTransaction());

	DFW_perform_system_work(tdbb);
}


USHORT INI_get_trig_flags(const TEXT* trig_name)
{
/*********************************************
 *
 *      I N I _ g e t _ t r i g _ f l a g s
 *
 *********************************************
 *
 * Functional description
 *      Return the trigger flags for a system trigger.
 *
 **************************************/
	for (const jrd_trg* trig = triggers; trig->trg_length > 0; trig++)
	{
		if (!strcmp(trig->trg_name, trig_name))
		{
			return (trig->trg_flags);
		}
	}

	return (0);
}


void INI_init(thread_db* tdbb)
{
/**************************************
 *
 *	I N I _ i n i t
 *
 **************************************
 *
 * Functional description
 *	Initialize in memory meta data.  Assume that all meta data
 *	fields exist in the database even if this is not the case.
 *	Do not fill in the format length or the address in each
 *	format descriptor.
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();
	CHECK_DBB(dbb);

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

	const int* fld;
	for (const int* relfld = relfields; relfld[RFLD_R_NAME]; relfld = fld + 1)
	{
		jrd_rel* relation = MET_relation(tdbb, relfld[RFLD_R_ID]);
		relation->rel_flags |= REL_system;
		relation->rel_flags |= MET_get_rel_flags_from_TYPE(relfld[RFLD_R_TYPE]);
		relation->rel_name = names[relfld[RFLD_R_NAME]];
		int n = 0;
		for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH)
		{
			n++;
		}

		// Set a flag if their is a trigger on the relation.  Later we may need to compile it.

		for (const jrd_trg* trigger = triggers; trigger->trg_relation; trigger++)
		{
			if (relation->rel_name == names[trigger->trg_relation])
			{
				relation->rel_flags |= REL_sys_triggers;
				break;
			}
		}

		vec<jrd_fld*>* fields = vec<jrd_fld*>::newVector(*pool, n);
		relation->rel_fields = fields;
		vec<jrd_fld*>::iterator itr = fields->begin();
		Format* format = Format::newFormat(*pool, n);
		relation->rel_current_format = format;
		vec<Format*>* formats = vec<Format*>::newVector(*pool, 1);
		relation->rel_formats = formats;
		(*formats)[0] = format;
		Format::fmt_desc_iterator desc = format->fmt_desc.begin();

		for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH, ++desc, ++itr)
		{
			const gfld* gfield = &gfields[fld[RFLD_F_ID]];
			desc->dsc_length = gfield->gfld_length;
			if (gfield->gfld_dtype == dtype_varying)
			{
				fb_assert(desc->dsc_length <= MAX_USHORT - sizeof(USHORT));
				desc->dsc_length += sizeof(USHORT);
			}

			desc->dsc_dtype = gfield->gfld_dtype;

			if (desc->isText())
			{
				if (!get_charset_by_text_type(desc->dsc_sub_type, gfield->gfld_sub_type))
					desc->dsc_sub_type = CS_NONE;
			}
			else
				desc->dsc_sub_type = gfield->gfld_sub_type;

			if (desc->dsc_dtype == dtype_blob && desc->dsc_sub_type == isc_blob_text)
				desc->dsc_scale = CS_METADATA;	// blob charset

			jrd_fld* field = FB_NEW_POOL(*pool) jrd_fld(*pool);
			*itr = field;
			field->fld_name = names[fld[RFLD_F_NAME]];
		}
	}
}


void INI_init2(thread_db* tdbb)
{
/**************************************
 *
 *	I N I _ i n i t 2
 *
 **************************************
 *
 * Functional description
 *	Re-initialize in memory meta data.  Fill in
 *	format 0 based on the minor ODS version of
 *	the database when it was created.
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* const dbb = tdbb->getDatabase();

	const USHORT major_version = dbb->dbb_ods_version;
	const USHORT minor_version = dbb->dbb_minor_version;
	vec<jrd_rel*>* vector = tdbb->getAttachment()->att_relations;

	const int* fld;
	for (const int* relfld = relfields; relfld[RFLD_R_NAME]; relfld = fld + 1)
	{
		if (relfld[RFLD_R_ODS] > ENCODE_ODS(major_version, minor_version))
		{
			// free the space allocated for RDB$ROLES

			const USHORT id = relfld[RFLD_R_ID];
			jrd_rel* relation = (*vector)[id];
			delete relation->rel_current_format;
			delete relation->rel_formats;
			delete relation->rel_fields;
			delete relation;
			(*vector)[id] = NULL;
			fld = relfld + RFLD_RPT;
			while (fld[RFLD_F_NAME])
			{
				fld += RFLD_F_LENGTH;
			}
		}
		else
		{
			jrd_rel* relation = MET_relation(tdbb, relfld[RFLD_R_ID]);
			Format* format = relation->rel_current_format;

			int n = 0;
			for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH)
			{
				if (ENCODE_ODS(major_version, minor_version) >= fld[RFLD_F_ODS])
					n++;
			}

			relation->rel_fields->resize(n);
			format->fmt_count = n; // We are using less than the allocated members.
			format->fmt_length = FLAG_BYTES(n);
			Format::fmt_desc_iterator desc = format->fmt_desc.begin();
			for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH, ++desc)
			{
				if (n-- > 0)
				{
					format->fmt_length = MET_align(&(*desc), format->fmt_length);
					desc->dsc_address = (UCHAR*)(IPTR) format->fmt_length;
					format->fmt_length += desc->dsc_length;
				}
			}
		}
	}
}


// Load system objects into DSQL metadata cache.
void INI_init_dsql(thread_db* tdbb, dsql_dbb* database)
{
	SET_TDBB(tdbb);

	Database* const dbb = tdbb->getDatabase();
	const USHORT majorVersion = dbb->dbb_ods_version;
	const USHORT minorVersion = dbb->dbb_minor_version;
	const int* fld;

	// Load relation and fields.

	for (const int* relfld = relfields; relfld[RFLD_R_NAME]; relfld = fld + 1)
	{
		if (relfld[RFLD_R_ODS] > ENCODE_ODS(majorVersion, minorVersion))
			continue;

		dsql_rel* relation = FB_NEW_POOL(database->dbb_pool) dsql_rel(database->dbb_pool);

		relation->rel_id = relfld[RFLD_R_ID];
		relation->rel_name = names[relfld[RFLD_R_NAME]];
		relation->rel_owner = DBA_USER_NAME;
		relation->rel_dbkey_length = 8;

		dsql_fld** ptr = &relation->rel_fields;
		int n = 0;

		for (fld = relfld + RFLD_RPT; fld[RFLD_F_NAME]; fld += RFLD_F_LENGTH)
		{
			if (ENCODE_ODS(majorVersion, minorVersion) < fld[RFLD_F_ODS])
				continue;

			dsql_fld* field = FB_NEW_POOL(database->dbb_pool) dsql_fld(database->dbb_pool);
			field->fld_id = n++;

			*ptr = field;
			ptr = &field->fld_next;

			// get field information

			const gfld* gfield = &gfields[fld[RFLD_F_ID]];

			field->fld_name = names[fld[RFLD_F_NAME]];
			field->fieldSource = names[gfield->gfld_name];
			field->length = gfield->gfld_length;
			field->scale = 0;
			field->subType = gfield->gfld_sub_type;
			field->fld_relation = relation;

			field->dtype = gfield->gfld_dtype;

			if (field->dtype == dtype_varying)
				field->length += sizeof(USHORT);
			else if (field->dtype == dtype_blob)
			{
				field->segLength = 80;
				if (gfield->gfld_sub_type == isc_blob_text)
					field->charSetId = CS_METADATA;
			}

			if (DTYPE_IS_TEXT(gfield->gfld_dtype))
			{
				if (get_charset_by_text_type(field->charSetId.value, gfield->gfld_sub_type))
					field->charSetId.specified = true;
			}

			if (gfield->gfld_nullable)
				field->flags |= FLD_nullable;

			field->flags |= FLD_system;
		}

		database->dbb_relations.put(relation->rel_name, relation);
		MET_dsql_cache_use(tdbb, SYM_relation, relation->rel_name);
	}

	// Load internal character sets and collations, necessary for engine operation.

	for (const IntlManager::CharSetDefinition* csDef = IntlManager::defaultCharSets;
		 csDef->name; ++csDef)
	{
		if (csDef->id > ttype_last_internal)
			continue;

		dsql_intlsym* csSymbol = FB_NEW_POOL(database->dbb_pool) dsql_intlsym(database->dbb_pool);
		csSymbol->intlsym_name = csDef->name;
		csSymbol->intlsym_charset_id = csDef->id;
		csSymbol->intlsym_collate_id = 0;
		csSymbol->intlsym_ttype =
			INTL_CS_COLL_TO_TTYPE(csSymbol->intlsym_charset_id, csSymbol->intlsym_collate_id);
		csSymbol->intlsym_bytes_per_char = csDef->maxBytes;

		// Mark the charset as invalid to reload it ASAP. This is done because we cannot know here
		// if the user has altered its default collation.
		csSymbol->intlsym_flags = INTLSYM_dropped;

		database->dbb_charsets.put(csDef->name, csSymbol);
		database->dbb_charsets_by_id.put(csSymbol->intlsym_charset_id, csSymbol);
		MET_dsql_cache_use(tdbb, SYM_intlsym_charset, csDef->name);

		for (const IntlManager::CollationDefinition* colDef = IntlManager::defaultCollations;
			 colDef->name; ++colDef)
		{
			if (colDef->charSetId != csDef->id)
				continue;

			dsql_intlsym* colSymbol = FB_NEW_POOL(database->dbb_pool) dsql_intlsym(database->dbb_pool);
			colSymbol->intlsym_name = colDef->name;
			colSymbol->intlsym_flags = 0;
			colSymbol->intlsym_charset_id = csDef->id;
			colSymbol->intlsym_collate_id = colDef->collationId;
			colSymbol->intlsym_ttype =
				INTL_CS_COLL_TO_TTYPE(colSymbol->intlsym_charset_id, colSymbol->intlsym_collate_id);
			colSymbol->intlsym_bytes_per_char = csDef->maxBytes;

			database->dbb_collations.put(colDef->name, colSymbol);
			MET_dsql_cache_use(tdbb, SYM_intlsym_collation, colDef->name);
		}
	}
}


static void add_index_set(thread_db* tdbb)
{
/**************************************
 *
 *	a d d _ i n d e x _ s e t
 *
 **************************************
 *
 * Functional description
 *	Add system indices.  If update_ods is true we are performing
 *	an ODS update, and only add the indices marked as newer than
 *	ODS (major_version,minor_version).
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	index_desc idx;

	AutoRequest handle1, handle2, handle3;

	for (int n = 0; n < SYSTEM_INDEX_COUNT; n++)
	{
		const ini_idx_t* index = &indices[n];
		jrd_rel* relation = MET_relation(tdbb, index->ini_idx_relid);

		MetaName indexName;
		indexName.printf("RDB$INDEX_%d", index->ini_idx_index_id);

		STORE(REQUEST_HANDLE handle1) X IN RDB$INDICES
			PAD(relation->rel_name.c_str(), X.RDB$RELATION_NAME);
			PAD(indexName.c_str(), X.RDB$INDEX_NAME);
			X.RDB$UNIQUE_FLAG = index->ini_idx_flags & idx_unique;
			X.RDB$SEGMENT_COUNT = index->ini_idx_segment_count;

			if (index->ini_idx_flags & idx_descending)
			{
				X.RDB$INDEX_TYPE.NULL = FALSE;
				X.RDB$INDEX_TYPE = 1;
			}
			else
				X.RDB$INDEX_TYPE.NULL = TRUE;

			X.RDB$SYSTEM_FLAG = RDB_system;
			X.RDB$SYSTEM_FLAG.NULL = FALSE;
			X.RDB$INDEX_INACTIVE = 0;

			// Store each segment for the index
			index_desc::idx_repeat* tail = idx.idx_rpt;
			for (USHORT position = 0; position < index->ini_idx_segment_count; position++, tail++)
			{
				const ini_idx_t::ini_idx_segment_t* segment = &index->ini_idx_segment[position];
				STORE(REQUEST_HANDLE handle2) Y IN RDB$INDEX_SEGMENTS
					jrd_fld* field = (*relation->rel_fields)[segment->ini_idx_rfld_id];

					Y.RDB$FIELD_POSITION = position;
					PAD(X.RDB$INDEX_NAME, Y.RDB$INDEX_NAME);
					PAD(field->fld_name.c_str(), Y.RDB$FIELD_NAME);
					tail->idx_field = segment->ini_idx_rfld_id;
					tail->idx_itype = segment->ini_idx_type;
				    tail->idx_selectivity = 0;
				END_STORE
			}

			idx.idx_count = index->ini_idx_segment_count;
			idx.idx_flags = index->ini_idx_flags;
			SelectivityList selectivity(*tdbb->getDefaultPool());

			IDX_create_index(tdbb, relation, &idx, indexName.c_str(), NULL,
				attachment->getSysTransaction(), selectivity);

			X.RDB$INDEX_ID = idx.idx_id + 1;
		END_STORE

		if (index->ini_idx_flags & idx_unique)
		{
			STORE(REQUEST_HANDLE handle3) RC IN RDB$RELATION_CONSTRAINTS
				PAD(indexName.c_str(), RC.RDB$CONSTRAINT_NAME);
				PAD(indexName.c_str(), RC.RDB$INDEX_NAME);
				PAD(relation->rel_name.c_str(), RC.RDB$RELATION_NAME);
				strcpy(RC.RDB$CONSTRAINT_TYPE, UNIQUE_CNSTRT);
				strcpy(RC.RDB$DEFERRABLE, "NO");
				strcpy(RC.RDB$INITIALLY_DEFERRED, "NO");
			END_STORE
		}
	}
}


// The caller used an UCHAR* to store the acl, it was converted to TEXT* to
// be passed to this function, only to be converted to UCHAR* to be passed
// to BLB_put_segment. Therefore, "acl" was changed to UCHAR* as param.
static void add_security_to_sys_rel(thread_db*	tdbb,
									AutoRequest& reqAddSC,
									AutoRequest& reqModObjSC,
									AutoRequest& reqInsUserPriv,
									const MetaName&	user_name,
									const TEXT*	rel_name,
									const USHORT	acl_length,
									const UCHAR*	acl)
{
/**************************************
 *
 *      a d d _ s e c u r i t y _ t o _ s y s _ r e l
 *
 **************************************
 *
 * Functional description
 *
 *      Add security to system relations. Only the owner of the
 *      database has SELECT/INSERT/UPDATE/DELETE privileges on
 *      any system relations. Any other users only has SELECT
 *      privilege.
 *
 **************************************/
	MetaName security_class, default_class;

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

	security_class.printf("%s%" SQUADFORMAT, SQL_SECCLASS_PREFIX,
						  DPM_gen_id(tdbb, MET_lookup_generator(tdbb, SQL_SECCLASS_GENERATOR), false, 1));

	default_class.printf("%s%" SQUADFORMAT, DEFAULT_CLASS,
						 DPM_gen_id(tdbb, MET_lookup_generator(tdbb, DEFAULT_CLASS), false, 1));


	add_security_class(tdbb, reqAddSC, security_class, acl_length, acl);
	add_security_class(tdbb, reqAddSC, default_class, acl_length, acl);

	FOR(REQUEST_HANDLE reqModObjSC) REL IN RDB$RELATIONS
		WITH REL.RDB$RELATION_NAME EQ rel_name
	{
		MODIFY REL USING
			REL.RDB$SECURITY_CLASS.NULL = FALSE;
			PAD(security_class.c_str(), REL.RDB$SECURITY_CLASS);

			REL.RDB$DEFAULT_CLASS.NULL = FALSE;
			PAD(default_class.c_str(), REL.RDB$DEFAULT_CLASS);
		END_MODIFY
	}
	END_FOR

	for (int cnt = 0; cnt < 6; cnt++)
	{
		STORE(REQUEST_HANDLE reqInsUserPriv) PRIV IN RDB$USER_PRIVILEGES
			switch (cnt)
			{
			case 0:
				strcpy(PRIV.RDB$USER, user_name.c_str());
				PRIV.RDB$PRIVILEGE[0] = 'S';
				PRIV.RDB$GRANT_OPTION = WITH_GRANT_OPTION;
				break;
			case 1:
				strcpy(PRIV.RDB$USER, user_name.c_str());
				PRIV.RDB$PRIVILEGE[0] = 'I';
				PRIV.RDB$GRANT_OPTION = WITH_GRANT_OPTION;
				break;
			case 2:
				strcpy(PRIV.RDB$USER, user_name.c_str());
				PRIV.RDB$PRIVILEGE[0] = 'U';
				PRIV.RDB$GRANT_OPTION = WITH_GRANT_OPTION;
				break;
			case 3:
				strcpy(PRIV.RDB$USER, user_name.c_str());
				PRIV.RDB$PRIVILEGE[0] = 'D';
				PRIV.RDB$GRANT_OPTION = WITH_GRANT_OPTION;
				break;
			case 4:
				strcpy(PRIV.RDB$USER, user_name.c_str());
				PRIV.RDB$PRIVILEGE[0] = 'R';
				PRIV.RDB$GRANT_OPTION = WITH_GRANT_OPTION;
				break;
			default:
				strcpy(PRIV.RDB$USER, "PUBLIC");
				PRIV.RDB$PRIVILEGE[0] = 'S';
				PRIV.RDB$GRANT_OPTION = 0;
				break;
			}
			PRIV.RDB$PRIVILEGE[1] = 0;
			PRIV.RDB$GRANTOR.NULL = TRUE;
			strcpy(PRIV.RDB$RELATION_NAME, rel_name);
			PRIV.RDB$FIELD_NAME.NULL = TRUE;
			PRIV.RDB$USER_TYPE = obj_user;
			PRIV.RDB$OBJECT_TYPE = obj_relation;
		END_STORE
	}
}


// Add security to system objects.
static void add_security_to_sys_obj(thread_db* tdbb,
									AutoRequest& reqAddSC,
									AutoRequest& reqModObjSC,
									AutoRequest& reqInsUserPriv,
									const MetaName& user_name,
									USHORT obj_type,
									const MetaName& obj_name,
									USHORT acl_length,
									const UCHAR* acl)
{
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	MetaName security_class;
	security_class.printf("%s%" SQUADFORMAT, SQL_SECCLASS_PREFIX,
		DPM_gen_id(tdbb, MET_lookup_generator(tdbb, SQL_SECCLASS_GENERATOR), false, 1));

	add_security_class(tdbb, reqAddSC, security_class, acl_length, acl);

	bool needsUsagePrivileges = false, needsExecPrivileges = false;

	if (obj_type == obj_field)
	{
		FOR(REQUEST_HANDLE reqModObjSC) FLD IN RDB$FIELDS
			WITH FLD.RDB$FIELD_NAME EQ obj_name.c_str()
		{
			MODIFY FLD USING
				FLD.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), FLD.RDB$SECURITY_CLASS);
			END_MODIFY

			needsUsagePrivileges = true;
		}
		END_FOR
	}
	else if (obj_type == obj_charset)
	{
		FOR(REQUEST_HANDLE reqModObjSC) CS IN RDB$CHARACTER_SETS
			WITH CS.RDB$CHARACTER_SET_NAME EQ obj_name.c_str()
		{
			MODIFY CS USING
				CS.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), CS.RDB$SECURITY_CLASS);
			END_MODIFY

			needsUsagePrivileges = true;
		}
		END_FOR
	}
	else if (obj_type == obj_collation)
	{
		FOR(REQUEST_HANDLE reqModObjSC) COLL IN RDB$COLLATIONS
			WITH COLL.RDB$COLLATION_NAME EQ obj_name.c_str()
		{
			MODIFY COLL USING
				COLL.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), COLL.RDB$SECURITY_CLASS);
			END_MODIFY

			needsUsagePrivileges = true;
		}
		END_FOR
	}
	else if (obj_type == obj_exception)
	{
		FOR(REQUEST_HANDLE reqModObjSC) XCP IN RDB$EXCEPTIONS
			WITH XCP.RDB$EXCEPTION_NAME EQ obj_name.c_str()
		{
			MODIFY XCP USING
				XCP.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), XCP.RDB$SECURITY_CLASS);
			END_MODIFY

			needsUsagePrivileges = true;
		}
		END_FOR
	}
	else if (obj_type == obj_generator)
	{
		FOR(REQUEST_HANDLE reqModObjSC) GEN IN RDB$GENERATORS
			WITH GEN.RDB$GENERATOR_NAME EQ obj_name.c_str()
		{
			MODIFY GEN USING
				GEN.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), GEN.RDB$SECURITY_CLASS);
			END_MODIFY

			needsUsagePrivileges = true;
		}
		END_FOR
	}
	else if (obj_type == obj_sql_role)
	{
		FOR(REQUEST_HANDLE reqModObjSC) R IN RDB$ROLES
			WITH R.RDB$ROLE_NAME EQ obj_name.c_str()
		{
			MODIFY R USING
				R.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), R.RDB$SECURITY_CLASS);
			END_MODIFY
		}
		END_FOR
	}
	else if (obj_type == obj_package_header)
	{
		FOR(REQUEST_HANDLE reqModObjSC) PKG IN RDB$PACKAGES
			WITH PKG.RDB$PACKAGE_NAME EQ obj_name.c_str()
		{
			MODIFY PKG USING
				PKG.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), PKG.RDB$SECURITY_CLASS);
			END_MODIFY

			needsExecPrivileges = true;
		}
		END_FOR
	}
	else if (obj_type == obj_database)
	{
		FOR(REQUEST_HANDLE reqModObjSC) DB IN RDB$DATABASE
		{
			MODIFY DB USING
				DB.RDB$SECURITY_CLASS.NULL = FALSE;
				PAD(security_class.c_str(), DB.RDB$SECURITY_CLASS);
			END_MODIFY
		}
		END_FOR
	}
	else
		fb_assert(false);

	const char* const privileges =
		needsUsagePrivileges ? USAGE_PRIVILEGES :
		needsExecPrivileges ? EXEC_PRIVILEGES :
		NULL;

	if (privileges)
	{
		fb_assert(user_name.hasData());
		fb_assert(obj_name.hasData());

		for (const char* p = privileges; *p; ++p)
		{
			STORE(REQUEST_HANDLE reqInsUserPriv) PRIV IN RDB$USER_PRIVILEGES
				PAD(user_name.c_str(), PRIV.RDB$USER);
				PAD(obj_name.c_str(), PRIV.RDB$RELATION_NAME);
				PRIV.RDB$PRIVILEGE[0] = *p;
				PRIV.RDB$PRIVILEGE[1] = 0;
				PRIV.RDB$GRANT_OPTION = WITH_GRANT_OPTION;
				PRIV.RDB$USER_TYPE = obj_user;
				PRIV.RDB$OBJECT_TYPE = obj_type;
				PRIV.RDB$GRANTOR.NULL = TRUE;
			END_STORE
		}
	}
}


static void store_admin_grant(thread_db* tdbb, const char* user, USHORT user_type,
	const char* object, USHORT object_type, const char* prvl, USHORT option, bool dflt)
{
	Attachment* attachment = tdbb->getAttachment();		// needed for preprocessed code
	AutoRequest handle;

	while (*prvl)
	{
		STORE(REQUEST_HANDLE handle) PRIV IN RDB$USER_PRIVILEGES
			PAD(user, PRIV.RDB$USER);
			PAD(object, PRIV.RDB$RELATION_NAME);

			if (dflt)
			{
				PRIV.RDB$FIELD_NAME[0] = 'D';
				PRIV.RDB$FIELD_NAME[1] = 0;
				PRIV.RDB$FIELD_NAME.NULL = FALSE;
			}
			else
				PRIV.RDB$FIELD_NAME.NULL = TRUE;

			PRIV.RDB$PRIVILEGE[0] = *prvl++;
			PRIV.RDB$PRIVILEGE[1] = 0;
			PRIV.RDB$GRANT_OPTION = option;
			PRIV.RDB$USER_TYPE = user_type;
			PRIV.RDB$OBJECT_TYPE = object_type;
			PRIV.RDB$GRANTOR.NULL = TRUE;
		END_STORE
	}
}


static void store_admin_role_grant(thread_db* tdbb, const char* user)
{
	store_admin_grant(tdbb, user, obj_user, ADMIN_ROLE, obj_sql_role, "M", 2, true);
}


static void store_admin_role(thread_db* tdbb, const MetaName& roleName, MetaName ownerName,
	UserId::Privileges* priv)
{
	if (!ownerName.hasData())
		ownerName = DBA_USER_NAME;

	string p;
	if (priv)
		priv->store(p.getBuffer(priv->BYTES_COUNT));
	else
		p = INI_owner_privileges();

	PreparedStatement::Builder sql;
	sql << "insert into rdb$roles(rdb$role_name, rdb$owner_name, rdb$system_flag, rdb$system_privileges)"
		<< "values (" << roleName << "," << ownerName << ", 1," << p << ")";

	Attachment* attachment = tdbb->getAttachment();
	jrd_tra* transaction = attachment->getSysTransaction();
	AutoPreparedStatement ps(attachment->prepareStatement(tdbb, transaction, sql));
	ps->execute(tdbb, transaction);
}


static void store_default_pub(thread_db* tdbb, const MetaName& ownerName)
{
	Attachment* const attachment = tdbb->getAttachment();
	AutoRequest handle;

	STORE(REQUEST_HANDLE handle) PUB IN RDB$PUBLICATIONS
	{
		PAD(DEFAULT_PUBLICATION, PUB.RDB$PUBLICATION_NAME);
		PUB.RDB$PUBLICATION_NAME.NULL = FALSE;

		PAD(ownerName.c_str(), PUB.RDB$OWNER_NAME);
		PUB.RDB$OWNER_NAME.NULL = FALSE;

		PUB.RDB$SYSTEM_FLAG = RDB_system;
		PUB.RDB$SYSTEM_FLAG.NULL = FALSE;

		PUB.RDB$ACTIVE_FLAG = 0;
		PUB.RDB$ACTIVE_FLAG.NULL = FALSE;

		PUB.RDB$AUTO_ENABLE = 0;
		PUB.RDB$AUTO_ENABLE.NULL = FALSE;
	}
	END_STORE
}


// Add security class.
static void add_security_class(thread_db* tdbb, AutoRequest& handle, const MetaName& class_name, USHORT acl_length, const UCHAR* acl)
{
	SET_TDBB(tdbb);
	Jrd::Attachment* const attachment = tdbb->getAttachment();

	bid blob_id;
	attachment->storeBinaryBlob(tdbb, attachment->getSysTransaction(), &blob_id,
		ByteChunk(acl, acl_length));

	STORE(REQUEST_HANDLE handle)
		CLS IN RDB$SECURITY_CLASSES
	{
		PAD(class_name.c_str(), CLS.RDB$SECURITY_CLASS);
		CLS.RDB$ACL = blob_id;
	}
	END_STORE
}


static void store_generator(thread_db* tdbb, const gen* generator, AutoRequest& handle,
	const MetaName& owner)
{
/**************************************
 *
 *	s t o r e _ g e n e r a t o r
 *
 **************************************
 *
 * Functional description
 *	Store the passed generator according to
 *	the information in the generator block.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	STORE(REQUEST_HANDLE handle) X IN RDB$GENERATORS
	{
		PAD(generator->gen_name, X.RDB$GENERATOR_NAME);
		X.RDB$GENERATOR_ID = generator->gen_id;
		X.RDB$SYSTEM_FLAG = RDB_system;
		X.RDB$SYSTEM_FLAG.NULL = FALSE;
		PAD(owner.c_str(), X.RDB$OWNER_NAME);
		X.RDB$OWNER_NAME.NULL = FALSE;
		X.RDB$INITIAL_VALUE = 0;
		X.RDB$INITIAL_VALUE.NULL = FALSE;
		if (generator->gen_description)
		{
		    attachment->storeMetaDataBlob(tdbb, attachment->getSysTransaction(), &X.RDB$DESCRIPTION,
				generator->gen_description);
			X.RDB$DESCRIPTION.NULL = FALSE;
		}
		else
			X.RDB$DESCRIPTION.NULL = TRUE;

		X.RDB$GENERATOR_INCREMENT = 0; // only sys gens have zero default increment
	}
	END_STORE
}


static void store_global_field(thread_db* tdbb, const gfld* gfield, AutoRequest& handle,
	const MetaName& owner)
{
/**************************************
 *
 *	s t o r e _ g l o b a l _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	Store a global field according to the
 *	passed information.
 *
 **************************************/
	SET_TDBB(tdbb);
	Database* dbb = tdbb->getDatabase();
	Jrd::Attachment* attachment = tdbb->getAttachment();

	STORE(REQUEST_HANDLE handle) X IN RDB$FIELDS
	{
		PAD(names[(USHORT)gfield->gfld_name], X.RDB$FIELD_NAME);
		X.RDB$FIELD_LENGTH = gfield->gfld_length;
		X.RDB$FIELD_SCALE = 0;
		X.RDB$SYSTEM_FLAG = RDB_system;
		X.RDB$SYSTEM_FLAG.NULL = FALSE;
		PAD(owner.c_str(), X.RDB$OWNER_NAME);
		X.RDB$OWNER_NAME.NULL = FALSE;
		X.RDB$FIELD_SUB_TYPE.NULL = TRUE;
		X.RDB$CHARACTER_SET_ID.NULL = TRUE;
		X.RDB$COLLATION_ID.NULL = TRUE;
		X.RDB$SEGMENT_LENGTH.NULL = TRUE;
		X.RDB$CHARACTER_LENGTH.NULL = TRUE;
		if (gfield->gfld_dflt_blr)
		{
		    attachment->storeBinaryBlob(tdbb, attachment->getSysTransaction(), &X.RDB$DEFAULT_VALUE,
				ByteChunk(gfield->gfld_dflt_blr, gfield->gfld_dflt_len));
			X.RDB$DEFAULT_VALUE.NULL = FALSE;
		}
		else
			X.RDB$DEFAULT_VALUE.NULL = TRUE;

		switch (gfield->gfld_dtype)
		{
		case dtype_timestamp:
			X.RDB$FIELD_TYPE = (int) blr_timestamp;
			break;

		case dtype_timestamp_tz:
			X.RDB$FIELD_TYPE = (int) blr_timestamp_tz;
			break;

		case dtype_sql_time:
			X.RDB$FIELD_TYPE = (int) blr_sql_time;
			break;

		case dtype_sql_date:
			X.RDB$FIELD_TYPE = (int) blr_sql_date;
			break;

		case dtype_short:
		case dtype_long:
		case dtype_int64:
			if (gfield->gfld_dtype == dtype_short)
				X.RDB$FIELD_TYPE = (int) blr_short;
			else if (gfield->gfld_dtype == dtype_long)
				X.RDB$FIELD_TYPE = (int) blr_long;
			else
				X.RDB$FIELD_TYPE = (int) blr_int64;

			if ((gfield->gfld_sub_type == dsc_num_type_numeric) ||
				(gfield->gfld_sub_type == dsc_num_type_decimal))
			{
				X.RDB$FIELD_SUB_TYPE.NULL = FALSE;
				X.RDB$FIELD_SUB_TYPE = gfield->gfld_sub_type;
			}
			break;

		case dtype_double:
			X.RDB$FIELD_TYPE = (int) blr_double;
			break;

		case dtype_text:
		case dtype_varying:
			if (gfield->gfld_dtype == dtype_text)
			{
				X.RDB$FIELD_TYPE = (int) blr_text;
			}
			else
			{
				X.RDB$FIELD_TYPE = (int) blr_varying;
			}
			switch (gfield->gfld_sub_type)
			{
			case dsc_text_type_metadata:
				X.RDB$CHARACTER_SET_ID.NULL = FALSE;
				X.RDB$CHARACTER_SET_ID = CS_METADATA;
				X.RDB$COLLATION_ID.NULL = FALSE;
				X.RDB$COLLATION_ID = COLLATE_NONE;
				X.RDB$FIELD_SUB_TYPE.NULL = FALSE;
				X.RDB$FIELD_SUB_TYPE = gfield->gfld_sub_type;
				X.RDB$CHARACTER_LENGTH.NULL = FALSE;
				X.RDB$CHARACTER_LENGTH = X.RDB$FIELD_LENGTH / METADATA_BYTES_PER_CHAR;
				break;
			case dsc_text_type_ascii:
				X.RDB$CHARACTER_SET_ID.NULL = FALSE;
				X.RDB$CHARACTER_SET_ID = CS_ASCII;
				X.RDB$COLLATION_ID.NULL = FALSE;
				X.RDB$COLLATION_ID = COLLATE_NONE;
				X.RDB$FIELD_SUB_TYPE.NULL = FALSE;
				X.RDB$FIELD_SUB_TYPE = gfield->gfld_sub_type;
				break;
			case dsc_text_type_fixed:
				X.RDB$CHARACTER_SET_ID.NULL = FALSE;
				X.RDB$CHARACTER_SET_ID = CS_BINARY;
				X.RDB$COLLATION_ID.NULL = FALSE;
				X.RDB$COLLATION_ID = COLLATE_NONE;
				X.RDB$FIELD_SUB_TYPE.NULL = FALSE;
				X.RDB$FIELD_SUB_TYPE = gfield->gfld_sub_type;
				break;
			default:
				X.RDB$CHARACTER_SET_ID.NULL = FALSE;
				X.RDB$CHARACTER_SET_ID = CS_NONE;
				X.RDB$COLLATION_ID.NULL = FALSE;
				X.RDB$COLLATION_ID = COLLATE_NONE;
				break;
			}
			break;

		case dtype_blob:
			X.RDB$FIELD_TYPE = (int) blr_blob;
			X.RDB$FIELD_SUB_TYPE.NULL = FALSE;
			X.RDB$SEGMENT_LENGTH.NULL = FALSE;
			X.RDB$FIELD_SUB_TYPE = gfield->gfld_sub_type;
			X.RDB$SEGMENT_LENGTH = 80;
			if (gfield->gfld_sub_type == isc_blob_text)
			{
				X.RDB$CHARACTER_SET_ID.NULL = FALSE;
				X.RDB$CHARACTER_SET_ID = CS_METADATA;
			}
			break;

		case dtype_boolean:
			X.RDB$FIELD_TYPE = (int) blr_bool;
			break;

		default:
			fb_assert(FALSE);
			break;
		}

		// Acknowledge not NULL sys fields
		X.RDB$NULL_FLAG.NULL = FALSE;
		X.RDB$NULL_FLAG = !gfield->gfld_nullable;
	}
	END_STORE
}


static void store_intlnames(thread_db* tdbb, const MetaName& owner)
{
/**************************************
 *
 *	s t o r e _ i n t l n a m e s
 *
 **************************************
 *
 * Functional description
 *	Store symbolic names & information for international
 *	character sets & collations.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	AutoRequest handle;

	for (const IntlManager::CharSetDefinition* charSet = IntlManager::defaultCharSets;
		 charSet->name; ++charSet)
	{
		STORE(REQUEST_HANDLE handle) X IN RDB$CHARACTER_SETS USING
		{
			PAD(charSet->name, X.RDB$CHARACTER_SET_NAME);
			PAD(charSet->name, X.RDB$DEFAULT_COLLATE_NAME);
			X.RDB$CHARACTER_SET_ID = charSet->id;
			X.RDB$BYTES_PER_CHARACTER = charSet->maxBytes;
			X.RDB$SYSTEM_FLAG = RDB_system;
			X.RDB$SYSTEM_FLAG.NULL = FALSE;
			PAD(owner.c_str(), X.RDB$OWNER_NAME);
			X.RDB$OWNER_NAME.NULL = FALSE;
		}
		END_STORE
	}

	handle.reset();

	for (const IntlManager::CollationDefinition* collation = IntlManager::defaultCollations;
		collation->name; ++collation)
	{
		STORE(REQUEST_HANDLE handle) X IN RDB$COLLATIONS USING
		{
			PAD(collation->name, X.RDB$COLLATION_NAME);

			if (collation->baseName)
			{
				X.RDB$BASE_COLLATION_NAME.NULL = false;
				PAD(collation->baseName, X.RDB$BASE_COLLATION_NAME);
			}
			else
				X.RDB$BASE_COLLATION_NAME.NULL = true;

			X.RDB$CHARACTER_SET_ID = collation->charSetId;
			X.RDB$COLLATION_ID = collation->collationId;
			X.RDB$SYSTEM_FLAG = RDB_system;
			X.RDB$SYSTEM_FLAG.NULL = FALSE;
			PAD(owner.c_str(), X.RDB$OWNER_NAME);
			X.RDB$OWNER_NAME.NULL = FALSE;
			X.RDB$COLLATION_ATTRIBUTES = collation->attributes;

			if (collation->specificAttributes)
			{
				attachment->storeMetaDataBlob(tdbb, attachment->getSysTransaction(),
					&X.RDB$SPECIFIC_ATTRIBUTES, collation->specificAttributes);
				X.RDB$SPECIFIC_ATTRIBUTES.NULL = FALSE;
			}
			else
				X.RDB$SPECIFIC_ATTRIBUTES.NULL = TRUE;
		}
		END_STORE
	}
}


static void store_message(thread_db* tdbb, const trigger_msg* message, AutoRequest& handle)
{
/**************************************
 *
 *	s t o r e _ m e s s a g e
 *
 **************************************
 *
 * Functional description
 *	Store system trigger messages.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	// store the trigger

	STORE(REQUEST_HANDLE handle) X IN RDB$TRIGGER_MESSAGES
	{
		PAD(message->trigmsg_name, X.RDB$TRIGGER_NAME);
		X.RDB$MESSAGE_NUMBER = message->trigmsg_number;
		PAD(message->trigmsg_text, X.RDB$MESSAGE);
	}
	END_STORE
}


static void store_relation_field(thread_db*		tdbb,
								 const int*		fld,
								 const int*		relfld,
								 int			field_id,
								 AutoRequest&	handle)
{
/**************************************
 *
 *	s t o r e _ r e l a t i o n _ f i e l d
 *
 **************************************
 *
 * Functional description
 *	Store a local field according to the
 *	passed information.
 *
 **************************************/
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();

	STORE(REQUEST_HANDLE handle) X IN RDB$RELATION_FIELDS
	{
		const gfld* gfield = &gfields[fld[RFLD_F_ID]];
		PAD(names[relfld[RFLD_R_NAME]], X.RDB$RELATION_NAME);
		PAD(names[fld[RFLD_F_NAME]], X.RDB$FIELD_NAME);
		PAD(names[gfield->gfld_name], X.RDB$FIELD_SOURCE);
		X.RDB$FIELD_POSITION = field_id;
		X.RDB$FIELD_ID = field_id;
		X.RDB$SYSTEM_FLAG = RDB_system;
		X.RDB$SYSTEM_FLAG.NULL = FALSE;
		X.RDB$UPDATE_FLAG = fld[RFLD_F_UPDATE];
	}
	END_STORE
}


// Store system packages.
static void store_packages(thread_db* tdbb, const MetaName& owner)
{
	SET_TDBB(tdbb);
	Jrd::Attachment* attachment = tdbb->getAttachment();
	Database* const dbb = tdbb->getDatabase();
	const USHORT majorVersion = dbb->dbb_ods_version;
	const USHORT minorVersion = dbb->dbb_minor_version;

	AutoRequest packageHandle, procedureHandle, procedureParameterHandle;
	AutoRequest functionHandle, functionReturnHandle, functionArgumentHandle;
	SLONG procGen = MET_lookup_generator(tdbb, PROCEDURES_GENERATOR);
	SLONG funcGen = MET_lookup_generator(tdbb, FUNCTIONS_GENERATOR);

	for (auto& systemPackage : SystemPackage::get())
	{
		if (systemPackage.odsVersion > ENCODE_ODS(majorVersion, minorVersion))
			continue;

		STORE (REQUEST_HANDLE packageHandle) PKG IN RDB$PACKAGES
		{
			PAD(systemPackage.name, PKG.RDB$PACKAGE_NAME);

			PAD(owner.c_str(), PKG.RDB$OWNER_NAME);
			PKG.RDB$SYSTEM_FLAG = RDB_system;
			PKG.RDB$VALID_BODY_FLAG = TRUE;
		}
		END_STORE

		for (auto& procedure : systemPackage.procedures)
		{
			STORE (REQUEST_HANDLE procedureHandle) PRC IN RDB$PROCEDURES
			{
				PAD(systemPackage.name, PRC.RDB$PACKAGE_NAME);
				PAD(procedure.name, PRC.RDB$PROCEDURE_NAME);

				PAD(owner.c_str(), PRC.RDB$OWNER_NAME);
				PRC.RDB$SYSTEM_FLAG = RDB_system;

				PRC.RDB$PROCEDURE_ID = DPM_gen_id(tdbb, procGen, false, 1);

				PRC.RDB$PROCEDURE_INPUTS = (SSHORT) procedure.inputParameters.getCount();
				PRC.RDB$PROCEDURE_OUTPUTS = (SSHORT) procedure.outputParameters.getCount();
				PRC.RDB$PROCEDURE_TYPE = (SSHORT) procedure.type;
				PRC.RDB$PRIVATE_FLAG = FALSE;
				PRC.RDB$VALID_BLR = TRUE;
				PAD("SYSTEM", PRC.RDB$ENGINE_NAME);
			}
			END_STORE

			for (SSHORT parameterType = 0; parameterType <= 1; ++parameterType)
			{
				SSHORT paramNumber = -1;

				for (auto& parameter : parameterType == 0 ? procedure.inputParameters : procedure.outputParameters)
				{
					++paramNumber;

					STORE (REQUEST_HANDLE procedureParameterHandle) PP IN RDB$PROCEDURE_PARAMETERS
					{
						PAD(systemPackage.name, PP.RDB$PACKAGE_NAME);
						PAD(procedure.name, PP.RDB$PROCEDURE_NAME);
						PAD(parameter.name, PP.RDB$PARAMETER_NAME);

						PP.RDB$SYSTEM_FLAG = RDB_system;

						PP.RDB$PARAMETER_NUMBER = paramNumber;
						PP.RDB$PARAMETER_TYPE = parameterType;
						PP.RDB$PARAMETER_MECHANISM = (SSHORT) prm_mech_normal;
						PP.RDB$NULL_FLAG = !parameter.nullable;

						PAD(names[gfields[parameter.fieldId].gfld_name], PP.RDB$FIELD_SOURCE);
					}
					END_STORE
				}
			}
		}

		for (auto& function : systemPackage.functions)
		{
			STORE (REQUEST_HANDLE functionHandle) FUN IN RDB$FUNCTIONS
			{
				PAD(systemPackage.name, FUN.RDB$PACKAGE_NAME);
				PAD(function.name, FUN.RDB$FUNCTION_NAME);

				PAD(owner.c_str(), FUN.RDB$OWNER_NAME);
				FUN.RDB$SYSTEM_FLAG = RDB_system;

				FUN.RDB$FUNCTION_ID = DPM_gen_id(tdbb, funcGen, false, 1);

				FUN.RDB$RETURN_ARGUMENT = 0;
				FUN.RDB$PRIVATE_FLAG = FALSE;
				FUN.RDB$VALID_BLR = TRUE;
				PAD("SYSTEM", FUN.RDB$ENGINE_NAME);
			}
			END_STORE

			SSHORT paramNumber = 0;

			STORE (REQUEST_HANDLE functionReturnHandle) ARG IN RDB$FUNCTION_ARGUMENTS
			{
				PAD(systemPackage.name, ARG.RDB$PACKAGE_NAME);
				PAD(function.name, ARG.RDB$FUNCTION_NAME);

				ARG.RDB$SYSTEM_FLAG = RDB_system;

				ARG.RDB$ARGUMENT_POSITION = paramNumber;
				ARG.RDB$NULL_FLAG = !function.returnType.nullable;

				PAD(names[gfields[function.returnType.fieldId].gfld_name], ARG.RDB$FIELD_SOURCE);
			}
			END_STORE

			for (auto& parameter : function.parameters)
			{
				++paramNumber;

				STORE (REQUEST_HANDLE functionArgumentHandle) ARG IN RDB$FUNCTION_ARGUMENTS
				{
					PAD(systemPackage.name, ARG.RDB$PACKAGE_NAME);
					PAD(function.name, ARG.RDB$FUNCTION_NAME);
					PAD(parameter.name, ARG.RDB$ARGUMENT_NAME);

					ARG.RDB$SYSTEM_FLAG = RDB_system;

					ARG.RDB$ARGUMENT_POSITION = paramNumber;
					ARG.RDB$NULL_FLAG = !parameter.nullable;

					PAD(names[gfields[parameter.fieldId].gfld_name], ARG.RDB$FIELD_SOURCE);
				}
				END_STORE
			}
		}
	}
}


static void store_trigger(thread_db* tdbb, const jrd_trg* trigger, AutoRequest& handle)
{
/**************************************
 *
 *	s t o r e _ t r i g g e r
 *
 **************************************
 *
 * Functional description
 *	Store the trigger according to the
 *	information in the trigger block.
 *
 **************************************/
	SET_TDBB(tdbb);
	Attachment* attachment = tdbb->getAttachment();

	// indicate that the relation format needs revising
	dsc desc;
	desc.makeText(static_cast<USHORT>(strlen(names[trigger->trg_relation])), CS_METADATA,
		(UCHAR*) names[trigger->trg_relation]);
	DFW_post_system_work(tdbb, dfw_update_format, &desc, 0);

	// store the trigger

	STORE(REQUEST_HANDLE handle) X IN RDB$TRIGGERS
	{
		PAD(trigger->trg_name, X.RDB$TRIGGER_NAME);
		PAD(names[trigger->trg_relation], X.RDB$RELATION_NAME);
		X.RDB$TRIGGER_SEQUENCE = 0;
		X.RDB$SYSTEM_FLAG = RDB_system;
		X.RDB$SYSTEM_FLAG.NULL = FALSE;
		X.RDB$TRIGGER_TYPE = trigger->trg_type;
		X.RDB$FLAGS = trigger->trg_flags;
		attachment->storeBinaryBlob(tdbb, attachment->getSysTransaction(), &X.RDB$TRIGGER_BLR,
			ByteChunk(trigger->trg_blr, trigger->trg_length));
	}
	END_STORE
}


string INI_owner_privileges()
{
	return string(gfields[fld_system_privileges].gfld_length, '\xff');
}


static bool get_charset_by_text_type(SSHORT& charSet, const USHORT sub_type)
{
	switch (sub_type)
	{
	case dsc_text_type_metadata:
		charSet = CS_METADATA;
		break;
	case dsc_text_type_ascii:
		charSet = CS_ASCII;
		break;
	case dsc_text_type_fixed:
		charSet = CS_BINARY;
		break;
	default:
		return false;
	}
	return true;
}

