text: /*
   *	PROGRAM:	Dynamic SQL runtime support
   *	MODULE:		dsql.cpp
   *	DESCRIPTION:	Local processing for External entry points.
   *
   * The contents of this file are subject to the Interbase Public
   * License Version 1.0 (the "License"); you may not use this file
   * except in compliance with the License. You may obtain a copy
   * of the License at http://www.Inprise.com/IPL.html
   *
   * Software distributed under the License is distributed on an
   * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
   * or implied. See the License for the specific language governing
   * rights and limitations under the License.
   *
   * The Original Code was created by Inprise Corporation
   * and its predecessors. Portions created by Inprise Corporation are
   * Copyright (C) Inprise Corporation.
   *
   * All Rights Reserved.
   * Contributor(s): ______________________________________.
   * 2001.07.06 Sean Leyne - Code Cleanup, removed "#ifdef READONLY_DATABASE"
   *                         conditionals, as the engine now fully supports
   *                         readonly databases.
   * December 2001 Mike Nordell: Major overhaul to (try to) make it C++
   * 2001.6.3 Claudio Valderrama: fixed a bad behaved loop in get_plan_info()
   * and get_rsb_item() that caused a crash when plan info was requested.
   * 2001.6.9 Claudio Valderrama: Added nod_del_view, nod_current_role and nod_breakleave.
   * 2002.10.29 Nickolay Samofatov: Added support for savepoints
   * 2002.10.29 Sean Leyne - Removed obsolete "Netware" port
   * 2004.01.16 Vlad Horsun: added support for EXECUTE BLOCK statement
   * Adriano dos Santos Fernandes
   */
  
  #include "firebird.h"
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include "../dsql/dsql.h"
  #include "../jrd/ibase.h"
  #include "../jrd/align.h"
  #include "../jrd/intl.h"
  #include "../common/intlobj_new.h"
  #include "../jrd/jrd.h"
  #include "../jrd/status.h"
  #include "../common/CharSet.h"
  #include "../dsql/Parser.h"
  #include "../dsql/ddl_proto.h"
  #include "../dsql/dsql_proto.h"
  #include "../dsql/errd_proto.h"
  #include "../dsql/gen_proto.h"
  #include "../dsql/make_proto.h"
  #include "../dsql/movd_proto.h"
  #include "../dsql/pass1_proto.h"
  #include "../dsql/metd_proto.h"
  #include "../jrd/DataTypeUtil.h"
  #include "../jrd/blb_proto.h"
  #include "../jrd/cmp_proto.h"
  #include "../yvalve/gds_proto.h"
  #include "../jrd/inf_proto.h"
  #include "../jrd/ini_proto.h"
  #include "../jrd/intl_proto.h"
  #include "../jrd/jrd_proto.h"
  #include "../jrd/opt_proto.h"
  #include "../jrd/tra_proto.h"
  #include "../jrd/recsrc/RecordSource.h"
  #include "../jrd/trace/TraceManager.h"
  #include "../jrd/trace/TraceDSQLHelpers.h"
  #include "../common/classes/init.h"
  #include "../common/utils_proto.h"
  #include "../common/StatusArg.h"
  #include "../dsql/DsqlBatch.h"
  
  #ifdef HAVE_CTYPE_H
  #include <ctype.h>
  #endif
  
  using namespace Jrd;
  using namespace Firebird;
  
  
  static ULONG	get_request_info(thread_db*, dsql_req*, ULONG, UCHAR*);
  static dsql_dbb*	init(Jrd::thread_db*, Jrd::Attachment*);
  static dsql_req* prepareRequest(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, bool);
  static dsql_req* prepareStatement(thread_db*, dsql_dbb*, jrd_tra*, ULONG, const TEXT*, USHORT, bool);
  static UCHAR*	put_item(UCHAR, const USHORT, const UCHAR*, UCHAR*, const UCHAR* const);
  static void		release_statement(DsqlCompiledStatement* statement);
  static void		sql_info(thread_db*, dsql_req*, ULONG, const UCHAR*, ULONG, UCHAR*);
  static UCHAR*	var_info(const dsql_msg*, const UCHAR*, const UCHAR* const, UCHAR*,
  	const UCHAR* const, USHORT, bool);
  static void		checkD(IStatus*);
  
  static inline bool reqTypeWithCursor(DsqlCompiledStatement::Type type)
  {
  	switch (type)
  	{
  		case DsqlCompiledStatement::TYPE_SELECT:
  		case DsqlCompiledStatement::TYPE_SELECT_BLOCK:
  		case DsqlCompiledStatement::TYPE_SELECT_UPD:
  			return true;
  	}
  
  	return false;
  }
  
  #ifdef DSQL_DEBUG
  unsigned DSQL_debug = 0;
  #endif
  
  namespace
  {
  	const UCHAR record_info[] =
  	{
  		isc_info_req_update_count, isc_info_req_delete_count,
  		isc_info_req_select_count, isc_info_req_insert_count
  	};
  }	// namespace
  
  
  #ifdef DSQL_DEBUG
  IMPLEMENT_TRACE_ROUTINE(dsql_trace, "DSQL")
  #endif
  
  dsql_dbb::~dsql_dbb()
  {
  }
  
  
  // Execute a dynamic SQL statement.
  void DSQL_execute(thread_db* tdbb,
  			  	  jrd_tra** tra_handle,
  				  dsql_req* request,
  				  IMessageMetadata* in_meta, const UCHAR* in_msg,
  				  IMessageMetadata* out_meta, UCHAR* out_msg)
  {
  	SET_TDBB(tdbb);
  
  	Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  
  	const DsqlCompiledStatement* statement = request->getStatement();
  
  	if (statement->getFlags() & DsqlCompiledStatement::FLAG_ORPHAN)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
  		          Arg::Gds(isc_bad_req_handle));
  	}
  
  	// Only allow NULL trans_handle if we're starting a transaction or set session properties
  
  	if (!*tra_handle &&
  		statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS &&
  		statement->getType() != DsqlCompiledStatement::TYPE_SESSION_MANAGEMENT)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
  				  Arg::Gds(isc_bad_trans_handle));
  	}
  
  	// A select with a non zero output length is a singleton select
  	const bool singleton = reqTypeWithCursor(statement->getType()) && out_msg;
  
  	// If the request is a SELECT or blob statement then this is an open.
  	// Make sure the cursor is not already open.
  
  	if (reqTypeWithCursor(statement->getType()))
  	{
  		if (request->req_cursor)
  		{
  			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
  					  Arg::Gds(isc_dsql_cursor_open_err));
  		}
  
  		if (!singleton)
  			(Arg::Gds(isc_random) << "Cannot execute SELECT statement").raise();
  	}
  
  	request->req_transaction = *tra_handle;
  	request->execute(tdbb, tra_handle, in_meta, in_msg, out_meta, out_msg, singleton);
  }
  
  
  // Open a dynamic SQL cursor.
  DsqlCursor* DSQL_open(thread_db* tdbb,
  			   	   	  jrd_tra** tra_handle,
  			   	   	  dsql_req* request,
  			   	   	  IMessageMetadata* in_meta, const UCHAR* in_msg,
  			   	   	  IMessageMetadata* out_meta, ULONG flags)
  {
  	SET_TDBB(tdbb);
  
  	Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  
  	const DsqlCompiledStatement* statement = request->getStatement();
  
  	if (statement->getFlags() & DsqlCompiledStatement::FLAG_ORPHAN)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
  		          Arg::Gds(isc_bad_req_handle));
  	}
  
  	// Validate transaction handle
  
  	if (!*tra_handle)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
  				  Arg::Gds(isc_bad_trans_handle));
  	}
  
  	// Validate statement type
  
  	if (!reqTypeWithCursor(statement->getType()))
  		Arg::Gds(isc_no_cursor).raise();
  
  	// Validate cursor or batch being not already open
  
  	if (request->req_cursor)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
  				  Arg::Gds(isc_dsql_cursor_open_err));
  	}
  
  	if (request->req_batch)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
  				  Arg::Gds(isc_batch_open));
  	}
  
  	request->req_transaction = *tra_handle;
  	request->execute(tdbb, tra_handle, in_meta, in_msg, out_meta, NULL, false);
  
  	request->req_cursor = FB_NEW_POOL(request->getPool()) DsqlCursor(request, flags);
  	return request->req_cursor;
  }
  
  
  // Provide backward-compatibility
  void DsqlDmlRequest::setDelayedFormat(thread_db* tdbb, Firebird::IMessageMetadata* metadata)
  {
  	if (!needDelayedFormat)
  	{
  		status_exception::raise(
  			Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
  			Arg::Gds(isc_dsql_sqlda_err) <<
  			Arg::Gds(isc_req_sync));
  	}
  
  	needDelayedFormat = false;
  	delayedFormat = metadata;
  }
  
  
  // Fetch next record from a dynamic SQL cursor.
  bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer)
  {
  	SET_TDBB(tdbb);
  
  	Jrd::ContextPoolHolder context(tdbb, &getPool());
  
  	const DsqlCompiledStatement* statement = getStatement();
  
  	// if the cursor isn't open, we've got a problem
  	if (reqTypeWithCursor(statement->getType()))
  	{
  		if (!req_cursor)
  		{
  			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
  					  Arg::Gds(isc_dsql_cursor_err) <<
  					  Arg::Gds(isc_dsql_cursor_not_open));
  		}
  	}
  
  	if (!req_request)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
  				  Arg::Gds(isc_unprepared_stmt));
  	}
  
  	dsql_msg* message = (dsql_msg*) statement->getReceiveMsg();
  
  	// Set up things for tracing this call
  	Jrd::Attachment* att = req_dbb->dbb_attachment;
  	TraceDSQLFetch trace(att, this);
  
  	thread_db::TimerGuard timerGuard(tdbb, req_timer, false);
  	if (req_timer && req_timer->expired())
  		tdbb->checkCancelState(true);
  
  	UCHAR* dsqlMsgBuffer = req_msg_buffers[message->msg_buffer_number];
  	JRD_receive(tdbb, req_request, message->msg_number, message->msg_length, dsqlMsgBuffer);
  
  	const dsql_par* const eof = statement->getEof();
  	const USHORT* eofPtr = eof ? (USHORT*) (dsqlMsgBuffer + (IPTR) eof->par_desc.dsc_address) : NULL;
  	const bool eofReached = eof && !(*eofPtr);
  
  	if (eofReached)
  	{
  		if (req_timer)
  			req_timer->stop();
  
  		delayedFormat = NULL;
  		trace.fetch(true, ITracePlugin::RESULT_SUCCESS);
  		return false;
  	}
  
  	mapInOut(tdbb, true, message, delayedFormat, msgBuffer);
  	delayedFormat = NULL;
  
  	trace.fetch(false, ITracePlugin::RESULT_SUCCESS);
  	return true;
  }
  
  
  /**
  
   	DSQL_free_statement
  
      @brief	Release request for a dsql statement
  
  
      @param user_status
      @param req_handle
      @param option
  
   **/
  void DSQL_free_statement(thread_db* tdbb, dsql_req* request, USHORT option)
  {
  	SET_TDBB(tdbb);
  
  	Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  
  	const DsqlCompiledStatement* statement = request->getStatement();
  
  	if (option & DSQL_drop)
  	{
  		// Release everything associated with the request
  		dsql_req::destroy(tdbb, request, true);
  	}
  	/*
  	else if (option & DSQL_unprepare)
  	{
  		// Release everything but the request itself
  		dsql_req::destroy(tdbb, request, false);
  	}
  	*/
  	else if (option & DSQL_close)
  	{
  		// Just close the cursor associated with the request
  		if (reqTypeWithCursor(statement->getType()))
  		{
  			if (!request->req_cursor)
  			{
  				ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-501) <<
  						  Arg::Gds(isc_dsql_cursor_close_err));
  			}
  
  			DsqlCursor::close(tdbb, request->req_cursor);
  		}
  	}
  }
  
  
  /**
  
   	DSQL_prepare
  
      @brief	Prepare a statement for execution.
  
  
      @param user_status
      @param trans_handle
      @param req_handle
      @param length
      @param string
      @param dialect
      @param item_length
      @param items
      @param buffer_length
      @param buffer
  
   **/
  dsql_req* DSQL_prepare(thread_db* tdbb,
  					   Attachment* attachment, jrd_tra* transaction,
  					   ULONG length, const TEXT* string, USHORT dialect,
  					   Array<UCHAR>* items, Array<UCHAR>* buffer,
  					   bool isInternalRequest)
  {
  	SET_TDBB(tdbb);
  
  	dsql_dbb* database = init(tdbb, attachment);
  	dsql_req* request = NULL;
  
  	try
  	{
  		// Allocate a new request block and then prepare the request.
  
  		request = prepareRequest(tdbb, database, transaction, length, string, dialect,
  			isInternalRequest);
  
  		// Can not prepare a CREATE DATABASE/SCHEMA statement
  
  		const DsqlCompiledStatement* statement = request->getStatement();
  		if (statement->getType() == DsqlCompiledStatement::TYPE_CREATE_DB)
  		{
  			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-530) <<
  					  Arg::Gds(isc_dsql_crdb_prepare_err));
  		}
  
  		if (items && buffer)
  		{
  			Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  			sql_info(tdbb, request, items->getCount(), items->begin(),
  				buffer->getCount(), buffer->begin());
  		}
  
  		return request;
  	}
  	catch (const Firebird::Exception&)
  	{
  		if (request)
  		{
  			Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  			dsql_req::destroy(tdbb, request, true);
  		}
  		throw;
  	}
  }
  
  
  // Set a cursor name for a dynamic request.
  void DsqlDmlRequest::setCursor(thread_db* tdbb, const TEXT* name)
  {
  	SET_TDBB(tdbb);
  
  	Jrd::ContextPoolHolder context(tdbb, &getPool());
  
  	const size_t MAX_CURSOR_LENGTH = 132 - 1;
  	string cursor = name;
  
  	if (cursor.hasData() && cursor[0] == '\"')
  	{
  		// Quoted cursor names eh? Strip'em.
  		// Note that "" will be replaced with ".
  		// The code is very strange, because it doesn't check for "" really
  		// and thus deletes one isolated " in the middle of the cursor.
  		for (string::iterator i = cursor.begin(); i < cursor.end(); ++i)
  		{
  			if (*i == '\"')
  				cursor.erase(i);
  		}
  	}
  	else	// not quoted name
  	{
  		const string::size_type i = cursor.find(' ');
  		if (i != string::npos)
  			cursor.resize(i);
  
  		cursor.upper();
  	}
  
  	USHORT length = (USHORT) fb_utils::name_length(cursor.c_str());
  
  	if (!length)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
  				  Arg::Gds(isc_dsql_decl_err) <<
  				  Arg::Gds(isc_dsql_cursor_invalid));
  	}
  
  	if (length > MAX_CURSOR_LENGTH)
  		length = MAX_CURSOR_LENGTH;
  
  	cursor.resize(length);
  
  	// If there already is a different cursor by the same name, bitch
  
  	dsql_req* const* symbol = req_dbb->dbb_cursors.get(cursor);
  	if (symbol)
  	{
  		if (this == *symbol)
  			return;
  
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
  				  Arg::Gds(isc_dsql_decl_err) <<
  				  Arg::Gds(isc_dsql_cursor_redefined) << cursor);
  	}
  
  	// If there already is a cursor and its name isn't the same, ditto.
  	// We already know there is no cursor by this name in the hash table
  
  	if (req_cursor && req_cursor_name.hasData())
  	{
  		fb_assert(!symbol);
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-502) <<
  				  Arg::Gds(isc_dsql_decl_err) <<
  				  Arg::Gds(isc_dsql_cursor_redefined) << req_cursor_name);
  	}
  
  	if (req_cursor_name.hasData())
  		req_dbb->dbb_cursors.remove(req_cursor_name);
  	req_cursor_name = cursor;
  	req_dbb->dbb_cursors.put(cursor, this);
  }
  
  
  /**
  
   	DSQL_sql_info
  
      @brief	Provide information on dsql statement
  
  
      @param user_status
      @param req_handle
      @param item_length
      @param items
      @param info_length
      @param info
  
   **/
  void DSQL_sql_info(thread_db* tdbb,
  				   dsql_req* request,
  				   ULONG item_length, const UCHAR* items,
  				   ULONG info_length, UCHAR* info)
  {
  	SET_TDBB(tdbb);
  
  	Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  
  	sql_info(tdbb, request, item_length, items, info_length, info);
  }
  
  
  // Common part of prepare and execute a statement.
  void DSQL_execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tra** tra_handle,
  	ULONG length, const TEXT* string, USHORT dialect,
  	Firebird::IMessageMetadata* in_meta, const UCHAR* in_msg,
  	Firebird::IMessageMetadata* out_meta, UCHAR* out_msg,
  	bool isInternalRequest)
  {
  	SET_TDBB(tdbb);
  
  	dsql_dbb* const database = init(tdbb, attachment);
  	dsql_req* request = NULL;
  
  	try
  	{
  		request = prepareRequest(tdbb, database, *tra_handle, length, string, dialect,
  			isInternalRequest);
  
  		const DsqlCompiledStatement* statement = request->getStatement();
  
  		// Only allow NULL trans_handle if we're starting a transaction or set session properties
  
  		if (!*tra_handle &&
  			statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS &&
  			statement->getType() != DsqlCompiledStatement::TYPE_SESSION_MANAGEMENT)
  		{
  			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
  					  Arg::Gds(isc_bad_trans_handle));
  		}
  
  		Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  
  		// A select having cursor is a singleton select when executed immediate
  		const bool singleton = reqTypeWithCursor(statement->getType());
  		if (singleton && !(out_msg && out_meta))
  		{
  			ERRD_post(Arg::Gds(isc_dsql_sqlda_err) <<
  					  Arg::Gds(isc_dsql_no_output_sqlda));
  		}
  
  		request->req_transaction = *tra_handle;
  
  		request->execute(tdbb, tra_handle, in_meta, in_msg, out_meta, out_msg, singleton);
  
  		dsql_req::destroy(tdbb, request, true);
  	}
  	catch (const Firebird::Exception&)
  	{
  		if (request)
  		{
  			Jrd::ContextPoolHolder context(tdbb, &request->getPool());
  			dsql_req::destroy(tdbb, request, true);
  		}
  		throw;
  	}
  }
  
  
  void DsqlDmlRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, bool* destroyScratchPool,
  	ntrace_result_t* traceResult)
  {
  	{	// scope
  		Jrd::ContextPoolHolder scratchContext(tdbb, &scratch->getPool());
  		node = Node::doDsqlPass(scratch, node);
  	}
  
  	if (scratch->clientDialect > SQL_DIALECT_V5)
  		scratch->getStatement()->setBlrVersion(5);
  	else
  		scratch->getStatement()->setBlrVersion(4);
  
  	GEN_request(scratch, node);
  
  	// Create the messages buffers
  	for (FB_SIZE_T i = 0; i < scratch->ports.getCount(); ++i)
  	{
  		dsql_msg* message = scratch->ports[i];
  
  		// Allocate buffer for message
  		const ULONG newLen = message->msg_length + FB_DOUBLE_ALIGN - 1;
  		UCHAR* msgBuffer = FB_NEW_POOL(scratch->getStatement()->getPool()) UCHAR[newLen];
  		msgBuffer = FB_ALIGN(msgBuffer, FB_DOUBLE_ALIGN);
  		message->msg_buffer_number = req_msg_buffers.add(msgBuffer);
  	}
  
  	// have the access method compile the statement
  
  #ifdef DSQL_DEBUG
  	if (DSQL_debug & 64)
  	{
  		dsql_trace("Resulting BLR code for DSQL:");
  		gds__trace_raw("Statement:\n");
  		gds__trace_raw(statement->getSqlText()->c_str(), statement->getSqlText()->length());
  		gds__trace_raw("\nBLR:\n");
  		fb_print_blr(scratch->getBlrData().begin(),
  			(ULONG) scratch->getBlrData().getCount(),
  			gds__trace_printer, 0, 0);
  	}
  #endif
  
  	FbLocalStatus localStatus;
  
  	// check for warnings
  	if (tdbb->tdbb_status_vector->getState() & IStatus::STATE_WARNINGS)
  	{
  		// save a status vector
  		fb_utils::copyStatus(&localStatus, tdbb->tdbb_status_vector);
  		fb_utils::init_status(tdbb->tdbb_status_vector);
  	}
  
  	ISC_STATUS status = FB_SUCCESS;
  
  	try
  	{
  		JRD_compile(tdbb, scratch->getAttachment()->dbb_attachment, &req_request,
  			scratch->getBlrData().getCount(), scratch->getBlrData().begin(),
  			statement->getSqlText(),
  			scratch->getDebugData().getCount(), scratch->getDebugData().begin(),
  			(scratch->flags & DsqlCompilerScratch::FLAG_INTERNAL_REQUEST));
  	}
  	catch (const Exception&)
  	{
  		status = tdbb->tdbb_status_vector->getErrors()[1];
  		*traceResult = status == isc_no_priv ?
  			ITracePlugin::RESULT_UNAUTHORIZED : ITracePlugin::RESULT_FAILED;
  	}
  
  	// restore warnings (if there are any)
  	if (localStatus->getState() & IStatus::STATE_WARNINGS)
  	{
  		Arg::StatusVector cur(tdbb->tdbb_status_vector->getWarnings());
  		Arg::StatusVector saved(localStatus->getWarnings());
  		saved << cur;
  
  		tdbb->tdbb_status_vector->setWarnings2(saved.length(), saved.value());
  	}
  
  	// free blr memory
  	scratch->getBlrData().free();
  
  	if (status)
  		status_exception::raise(tdbb->tdbb_status_vector);
  
  	// We don't need the scratch pool anymore. Tell our caller to delete it.
  	node = NULL;
  	*destroyScratchPool = true;
  }
  
  // Execute a dynamic SQL statement.
  void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
  	Firebird::IMessageMetadata* inMetadata, const UCHAR* inMsg,
  	Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg,
  	bool singleton)
  {
  	if (!req_request)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
  				  Arg::Gds(isc_unprepared_stmt));
  	}
  
  	// If there is no data required, just start the request
  
  	const dsql_msg* message = statement->getSendMsg();
  	if (message)
  		mapInOut(tdbb, false, message, inMetadata, NULL, inMsg);
  
  	// we need to mapInOut() before tracing of execution start to let trace
  	// manager know statement parameters values
  	TraceDSQLExecute trace(req_dbb->dbb_attachment, this);
  
  	// Setup and start timeout timer
  	const bool have_cursor = reqTypeWithCursor(statement->getType()) && !singleton;
  
  	setupTimer(tdbb);
  	thread_db::TimerGuard timerGuard(tdbb, req_timer, !have_cursor);
  
  	if (!message)
  		JRD_start(tdbb, req_request, req_transaction);
  	else
  	{
  		UCHAR* msgBuffer = req_msg_buffers[message->msg_buffer_number];
  		JRD_start_and_send(tdbb, req_request, req_transaction, message->msg_number,
  			message->msg_length, msgBuffer);
  	}
  
  	// Selectable execute block should get the "proc fetch" flag assigned,
  	// which ensures that the savepoint stack is preserved while suspending
  	if (statement->getType() == DsqlCompiledStatement::TYPE_SELECT_BLOCK)
  		req_request->req_flags |= req_proc_fetch;
  
  	// TYPE_EXEC_BLOCK has no outputs so there are no out_msg
  	// supplied from client side, but TYPE_EXEC_BLOCK requires
  	// 2-byte message for EOS synchronization
  	const bool isBlock = (statement->getType() == DsqlCompiledStatement::TYPE_EXEC_BLOCK);
  
  	message = statement->getReceiveMsg();
  
  	if (outMetadata == DELAYED_OUT_FORMAT)
  	{
  		needDelayedFormat = true;
  		outMetadata = NULL;
  	}
  
  	if (outMetadata && message)
  		parseMetadata(outMetadata, message->msg_parameters);
  
  	if ((outMsg && message) || isBlock)
  	{
  		UCHAR temp_buffer[FB_DOUBLE_ALIGN * 2];
  		dsql_msg temp_msg(*getDefaultMemoryPool());
  
  		// Insure that the metadata for the message is parsed, regardless of
  		// whether anything is found by the call to receive.
  
  		UCHAR* msgBuffer = req_msg_buffers[message->msg_buffer_number];
  
  		if (!outMetadata && isBlock)
  		{
  			message = &temp_msg;
  			temp_msg.msg_number = 1;
  			temp_msg.msg_length = 2;
  			msgBuffer = FB_ALIGN(temp_buffer, FB_DOUBLE_ALIGN);
  		}
  
  		JRD_receive(tdbb, req_request, message->msg_number, message->msg_length, msgBuffer);
  
  		if (outMsg)
  			mapInOut(tdbb, true, message, NULL, outMsg);
  
  		// if this is a singleton select, make sure there's in fact one record
  
  		if (singleton)
  		{
  			USHORT counter;
  
  			// Create a temp message buffer and try two more receives.
  			// If both succeed then the first is the next record and the
  			// second is either another record or the end of record message.
  			// In either case, there's more than one record.
  
  			UCHAR* message_buffer = (UCHAR*) gds__alloc(message->msg_length);
  
  			ISC_STATUS status = FB_SUCCESS;
  			FbLocalStatus localStatus;
  
  			for (counter = 0; counter < 2 && !status; counter++)
  			{
  				localStatus->init();
  				AutoSetRestore<Jrd::FbStatusVector*> autoStatus(&tdbb->tdbb_status_vector, &localStatus);
  
  				try
  				{
  					JRD_receive(tdbb, req_request, message->msg_number,
  						message->msg_length, message_buffer);
  					status = FB_SUCCESS;
  				}
  				catch (Firebird::Exception&)
  				{
  					status = tdbb->tdbb_status_vector->getErrors()[1];
  				}
  			}
  
  			gds__free(message_buffer);
  
  			// two successful receives means more than one record
  			// a req_sync error on the first pass above means no records
  			// a non-req_sync error on any of the passes above is an error
  
  			if (!status)
  				status_exception::raise(Arg::Gds(isc_sing_select_err));
  			else if (status == isc_req_sync && counter == 1)
  				status_exception::raise(Arg::Gds(isc_stream_eof));
  			else if (status != isc_req_sync)
  				status_exception::raise(&localStatus);
  		}
  	}
  
  	switch (statement->getType())
  	{
  		case DsqlCompiledStatement::TYPE_UPDATE_CURSOR:
  			if (!req_request->req_records_updated)
  			{
  				ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-913) <<
  						  Arg::Gds(isc_deadlock) <<
  						  Arg::Gds(isc_update_conflict));
  			}
  			break;
  
  		case DsqlCompiledStatement::TYPE_DELETE_CURSOR:
  			if (!req_request->req_records_deleted)
  			{
  				ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-913) <<
  						  Arg::Gds(isc_deadlock) <<
  						  Arg::Gds(isc_update_conflict));
  			}
  			break;
  	}
  
  	trace.finish(have_cursor, ITracePlugin::RESULT_SUCCESS);
  }
  
  void DsqlDdlRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, bool* destroyScratchPool,
  	ntrace_result_t* traceResult)
  {
  	internalScratch = scratch;
  
  	scratch->flags |= DsqlCompilerScratch::FLAG_DDL;
  
  	try
  	{
  		node = Node::doDsqlPass(scratch, node);
  	}
  	catch (status_exception& ex)
  	{
  		rethrowDdlException(ex, false);
  	}
  
  	if (scratch->getAttachment()->dbb_read_only)
  		ERRD_post(Arg::Gds(isc_read_only_database));
  
  	if ((scratch->flags & DsqlCompilerScratch::FLAG_AMBIGUOUS_STMT) &&
  		scratch->getAttachment()->dbb_db_SQL_dialect != scratch->clientDialect)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-817) <<
  				  Arg::Gds(isc_ddl_not_allowed_by_db_sql_dial) <<
  					  Arg::Num(scratch->getAttachment()->dbb_db_SQL_dialect));
  	}
  
  	if (scratch->clientDialect > SQL_DIALECT_V5)
  		scratch->getStatement()->setBlrVersion(5);
  	else
  		scratch->getStatement()->setBlrVersion(4);
  }
  
  // Execute a dynamic SQL statement.
  void DsqlDdlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
  	Firebird::IMessageMetadata* inMetadata, const UCHAR* inMsg,
  	Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg,
  	bool singleton)
  {
  	TraceDSQLExecute trace(req_dbb->dbb_attachment, this);
  
  	fb_utils::init_status(tdbb->tdbb_status_vector);
  
  	// run all statements under savepoint control
  	{	// scope
  		AutoSavePoint savePoint(tdbb, req_transaction);
  
  		try
  		{
  			node->executeDdl(tdbb, internalScratch, req_transaction);
  		}
  		catch (status_exception& ex)
  		{
  			rethrowDdlException(ex, true);
  		}
  
  		savePoint.release();	// everything is ok
  	}
  
  	JRD_autocommit_ddl(tdbb, req_transaction);
  
  	trace.finish(false, ITracePlugin::RESULT_SUCCESS);
  }
  
  // Rethrow an exception with isc_no_meta_update and prefix codes.
  void DsqlDdlRequest::rethrowDdlException(status_exception& ex, bool metadataUpdate)
  {
  	Arg::StatusVector newVector;
  
  	if (metadataUpdate)
  		newVector << Arg::Gds(isc_no_meta_update);
  
  	node->putErrorPrefix(newVector);
  
  	const ISC_STATUS* status = ex.value();
  
  	if (status[1] == isc_no_meta_update)
  		status += 2;
  
  	newVector.append(Arg::StatusVector(status));
  
  	status_exception::raise(newVector);
  }
  
  
  void DsqlTransactionRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, bool* destroyScratchPool,
  	ntrace_result_t* /*traceResult*/)
  {
  	node = Node::doDsqlPass(scratch, node);
  
  	// Don't trace pseudo-statements (without requests associated).
  	req_traced = false;
  }
  
  // Execute a dynamic SQL statement.
  void DsqlTransactionRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
  	IMessageMetadata* /*inMetadata*/, const UCHAR* /*inMsg*/,
  	IMessageMetadata* /*outMetadata*/, UCHAR* /*outMsg*/,
  	bool /*singleton*/)
  {
  	node->execute(tdbb, this, traHandle);
  }
  
  
  void DsqlSessionManagementRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch, bool* destroyScratchPool,
  	ntrace_result_t* /*traceResult*/)
  {
  	node = Node::doDsqlPass(scratch, node);
  
  	// Don't trace pseudo-statements (without requests associated).
  	req_traced = false;
  }
  
  // Execute a dynamic SQL statement.
  void DsqlSessionManagementRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
  	Firebird::IMessageMetadata* inMetadata, const UCHAR* inMsg,
  	Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg,
  	bool singleton)
  {
  	node->execute(tdbb, this);
  }
  
  
  /**
  
   	get_request_info
  
      @brief	Get the records updated/deleted for record
  
  
      @param request
      @param buffer_length
      @param buffer
  
   **/
  static ULONG get_request_info(thread_db* tdbb, dsql_req* request, ULONG buffer_length, UCHAR* buffer)
  {
  	if (!request->req_request)	// DDL
  		return 0;
  
  	// get the info for the request from the engine
  
  	try
  	{
  		return INF_request_info(request->req_request, sizeof(record_info), record_info,
  			buffer_length, buffer);
  	}
  	catch (Firebird::Exception&)
  	{
  		return 0;
  	}
  }
  
  
  /**
  
   	init
  
      @brief	Initialize dynamic SQL.  This is called only once.
  
  
      @param db_handle
  
   **/
  static dsql_dbb* init(thread_db* tdbb, Jrd::Attachment* attachment)
  {
  	SET_TDBB(tdbb);
  
  	if (attachment->att_dsql_instance)
  		return attachment->att_dsql_instance;
  
  	MemoryPool& pool = *attachment->createPool();
  	dsql_dbb* const database = FB_NEW_POOL(pool) dsql_dbb(pool);
  	database->dbb_attachment = attachment;
  	attachment->att_dsql_instance = database;
  
  	INI_init_dsql(tdbb, database);
  
  	Database* const dbb = tdbb->getDatabase();
  	database->dbb_db_SQL_dialect =
  		(dbb->dbb_flags & DBB_DB_SQL_dialect_3) ? SQL_DIALECT_V6 : SQL_DIALECT_V5;
  
  	database->dbb_ods_version = dbb->dbb_ods_version;
  	database->dbb_minor_version = dbb->dbb_minor_version;
  
  	database->dbb_read_only = dbb->readOnly();
  
  #ifdef DSQL_DEBUG
  	DSQL_debug = Config::getTraceDSQL();
  #endif
  
  	return attachment->att_dsql_instance;
  }
  
  
  /**
  
   	mapInOut
  
      @brief	Map data from external world into message or
   	from message to external world.
  
  
      @param request
      @param toExternal
      @param message
      @param meta
      @param dsql_msg_buf
      @param in_dsql_msg_buf
  
   **/
  void dsql_req::mapInOut(thread_db* tdbb, bool toExternal, const dsql_msg* message,
  	IMessageMetadata* meta, UCHAR* dsql_msg_buf, const UCHAR* in_dsql_msg_buf)
  {
  	USHORT count = parseMetadata(meta, message->msg_parameters);
  
  	// Sanity check
  
  	if (count)
  	{
  		if (toExternal)
  		{
  			if (dsql_msg_buf == NULL)
  			{
  				ERRD_post(Arg::Gds(isc_dsql_sqlda_err) <<
  						  Arg::Gds(isc_dsql_no_output_sqlda));
  			}
  		}
  		else
  		{
  			if (in_dsql_msg_buf == NULL)
  			{
  				ERRD_post(Arg::Gds(isc_dsql_sqlda_err) <<
  						  Arg::Gds(isc_dsql_no_input_sqlda));
  			}
  		}
  	}
  
  	USHORT count2 = 0;
  
  	for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i)
  	{
  		dsql_par* parameter = message->msg_parameters[i];
  
  		if (parameter->par_index)
  		{
  			 // Make sure the message given to us is long enough
  
  			dsc desc;
  			if (!req_user_descs.get(parameter, desc))
  				desc.clear();
  
  			/***
  			ULONG length = (IPTR) desc.dsc_address + desc.dsc_length;
  			if (length > msg_length)
  			{
  				ERRD_post(Arg::Gds(isc_dsql_sqlda_err) <<
  					Arg::Gds(isc_random) << "Message buffer too short");
  			}
  			***/
  			if (!desc.dsc_dtype)
  			{
  				ERRD_post(Arg::Gds(isc_dsql_sqlda_err) <<
  					Arg::Gds(isc_dsql_datatype_err) <<
  					Arg::Gds(isc_dsql_sqlvar_index) << Arg::Num(parameter->par_index-1));
  			}
  
  			UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number];
  
  			SSHORT* flag = NULL;
  			dsql_par* const null_ind = parameter->par_null;
  			if (null_ind != NULL)
  			{
  				dsc userNullDesc;
  				if (!req_user_descs.get(null_ind, userNullDesc))
  					userNullDesc.clear();
  
  				const ULONG null_offset = (IPTR) userNullDesc.dsc_address;
  
  				/***
  				length = null_offset + sizeof(SSHORT);
  				if (length > msg_length)
  				{
  					ERRD_post(Arg::Gds(isc_dsql_sqlda_err)
  						<< Arg::Gds(isc_random) << "Message buffer too short");
  				}
  				***/
  
  				dsc nullDesc = null_ind->par_desc;
  				nullDesc.dsc_address = msgBuffer + (IPTR) nullDesc.dsc_address;
  
  				if (toExternal)
  				{
  					flag = reinterpret_cast<SSHORT*>(dsql_msg_buf + null_offset);
  					*flag = *reinterpret_cast<const SSHORT*>(nullDesc.dsc_address);
  				}
  				else
  				{
  					flag = reinterpret_cast<SSHORT*>(nullDesc.dsc_address);
  					*flag = *reinterpret_cast<const SSHORT*>(in_dsql_msg_buf + null_offset);
  				}
  			}
  
  			const bool notNull = (!flag || *flag >= 0);
  
  			dsc parDesc = parameter->par_desc;
  			parDesc.dsc_address = msgBuffer + (IPTR) parDesc.dsc_address;
  
  			if (toExternal)
  			{
  				desc.dsc_address = dsql_msg_buf + (IPTR) desc.dsc_address;
  
  				if (notNull)
  					MOVD_move(tdbb, &parDesc, &desc, toExternal);
  				else
  					memset(desc.dsc_address, 0, desc.dsc_length);
  			}
  			else if (notNull && !parDesc.isNull())
  			{
  				// Safe cast because desc is used as source only.
  				desc.dsc_address = const_cast<UCHAR*>(in_dsql_msg_buf) + (IPTR) desc.dsc_address;
  				MOVD_move(tdbb, &desc, &parDesc, toExternal);
  			}
  			else
  				memset(parDesc.dsc_address, 0, parDesc.dsc_length);
  
  			++count2;
  		}
  	}
  
  	if (count != count2)
  	{
  		ERRD_post(
  			Arg::Gds(isc_dsql_sqlda_err) <<
  			Arg::Gds(isc_dsql_wrong_param_num) << Arg::Num(count) <<Arg::Num(count2));
  	}
  
  	const DsqlCompiledStatement* statement = getStatement();
  	const dsql_par* parameter;
  
  	const dsql_par* dbkey;
  	if (!toExternal && (dbkey = statement->getParentDbKey()) &&
  		(parameter = statement->getDbKey()))
  	{
  		UCHAR* parentMsgBuffer = statement->getParentRequest() ?
  			statement->getParentRequest()->req_msg_buffers[dbkey->par_message->msg_buffer_number] : NULL;
  		UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number];
  
  		fb_assert(parentMsgBuffer);
  
  		dsc parentDesc = dbkey->par_desc;
  		parentDesc.dsc_address = parentMsgBuffer + (IPTR) parentDesc.dsc_address;
  
  		dsc desc = parameter->par_desc;
  		desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address;
  
  		MOVD_move(tdbb, &parentDesc, &desc, false);
  
  		dsql_par* null_ind = parameter->par_null;
  		if (null_ind != NULL)
  		{
  			desc = null_ind->par_desc;
  			desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address;
  
  			SSHORT* flag = (SSHORT*) desc.dsc_address;
  			*flag = 0;
  		}
  	}
  
  	const dsql_par* rec_version;
  	if (!toExternal && (rec_version = statement->getParentRecVersion()) &&
  		(parameter = statement->getRecVersion()))
  	{
  		UCHAR* parentMsgBuffer = statement->getParentRequest() ?
  			statement->getParentRequest()->req_msg_buffers[rec_version->par_message->msg_buffer_number] :
  			NULL;
  		UCHAR* msgBuffer = req_msg_buffers[parameter->par_message->msg_buffer_number];
  
  		fb_assert(parentMsgBuffer);
  
  		dsc parentDesc = rec_version->par_desc;
  		parentDesc.dsc_address = parentMsgBuffer + (IPTR) parentDesc.dsc_address;
  
  		dsc desc = parameter->par_desc;
  		desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address;
  
  		MOVD_move(tdbb, &parentDesc, &desc, false);
  
  		dsql_par* null_ind = parameter->par_null;
  		if (null_ind != NULL)
  		{
  			desc = null_ind->par_desc;
  			desc.dsc_address = msgBuffer + (IPTR) desc.dsc_address;
  
  			SSHORT* flag = (SSHORT*) desc.dsc_address;
  			*flag = 0;
  		}
  	}
  }
  
  
  /**
  
   	parseMetadata
  
      @brief	Parse the message of a request.
  
  
      @param request
      @param meta
      @param parameters_list
  
   **/
  USHORT dsql_req::parseMetadata(IMessageMetadata* meta, const Array<dsql_par*>& parameters_list)
  {
  	HalfStaticArray<const dsql_par*, 16> parameters;
  
  	for (FB_SIZE_T i = 0; i < parameters_list.getCount(); ++i)
  	{
  		dsql_par* param = parameters_list[i];
  
  		if (param->par_index)
  		{
  			if (param->par_index > parameters.getCount())
  				parameters.grow(param->par_index);
  			fb_assert(!parameters[param->par_index - 1]);
  			parameters[param->par_index - 1] = param;
  		}
  	}
  
  	// If there's no metadata, then the format of the current message buffer
  	// is identical to the format of the previous one.
  
  	if (!meta)
  		return parameters.getCount();
  
  	FbLocalStatus st;
  	unsigned count = meta->getCount(&st);
  	checkD(&st);
  
  	unsigned count2 = parameters.getCount();
  
  	if (count != count2)
  	{
  		ERRD_post(Arg::Gds(isc_dsql_sqlda_err) <<
  				  Arg::Gds(isc_dsql_wrong_param_num) <<Arg::Num(count2) << Arg::Num(count));
  	}
  
  	unsigned offset = 0;
  
  	for (USHORT index = 0; index < count; index++)
  	{
  		unsigned sqlType = meta->getType(&st, index);
  		checkD(&st);
  		unsigned sqlLength = meta->getLength(&st, index);
  		checkD(&st);
  
  		dsc desc;
  		desc.dsc_flags = 0;
  
  		unsigned dataOffset, nullOffset, dtype, dlength;
  		offset = fb_utils::sqlTypeToDsc(offset, sqlType, sqlLength,
  			&dtype, &dlength, &dataOffset, &nullOffset);
  		desc.dsc_dtype = dtype;
  		desc.dsc_length = dlength;
  
  		desc.dsc_scale = meta->getScale(&st, index);
  		checkD(&st);
  		desc.dsc_sub_type = meta->getSubType(&st, index);
  		checkD(&st);
  		unsigned textType = meta->getCharSet(&st, index);
  		checkD(&st);
  		desc.setTextType(textType);
  		desc.dsc_address = (UCHAR*)(IPTR) dataOffset;
  
  		const dsql_par* const parameter = parameters[index];
  		fb_assert(parameter);
  
  		// ASF: Older than 2.5 engine hasn't validating strings in DSQL. After this has been
  		// implemented in 2.5, selecting a NONE column with UTF-8 attachment charset started
  		// failing. The real problem is that the client encodes SQL_TEXT/SQL_VARYING using
  		// blr_text/blr_varying (i.e. with the connection charset). I'm reseting the charset
  		// here at the server as a way to make older (and not yet changed) client work
  		// correctly.
  		if (desc.isText() && desc.getTextType() == ttype_dynamic)
  			desc.setTextType(ttype_none);
  
  		req_user_descs.put(parameter, desc);
  
  		dsql_par* null = parameter->par_null;
  		if (null)
  		{
  			desc.clear();
  			desc.dsc_dtype = dtype_short;
  			desc.dsc_scale = 0;
  			desc.dsc_length = sizeof(SSHORT);
  			desc.dsc_address = (UCHAR*)(IPTR) nullOffset;
  
  			req_user_descs.put(null, desc);
  		}
  	}
  
  	return count;
  }
  
  
  // raise error if one present
  static void checkD(IStatus* st)
  {
  	if (st->getState() & IStatus::STATE_ERRORS)
  	{
  		ERRD_post(Arg::StatusVector(st));
  	}
  }
  
  
  // Prepare a request for execution. Return SQL status code.
  // Note: caller is responsible for pool handling.
  static dsql_req* prepareRequest(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
  	ULONG textLength, const TEXT* text, USHORT clientDialect, bool isInternalRequest)
  {
  	return prepareStatement(tdbb, database, transaction, textLength, text, clientDialect,
  		isInternalRequest);
  }
  
  
  // Prepare a statement for execution. Return SQL status code.
  // Note: caller is responsible for pool handling.
  static dsql_req* prepareStatement(thread_db* tdbb, dsql_dbb* database, jrd_tra* transaction,
  	ULONG textLength, const TEXT* text, USHORT clientDialect, bool isInternalRequest)
  {
  	if (text && textLength == 0)
  		textLength = static_cast<ULONG>(strlen(text));
  
  	TraceDSQLPrepare trace(database->dbb_attachment, transaction, textLength, text);
  
  	if (clientDialect > SQL_DIALECT_CURRENT)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
  				  Arg::Gds(isc_wish_list));
  	}
  
  	if (!text || textLength == 0)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
  				  // Unexpected end of command
  				  // CVC: Nothing will be line 1, column 1 for the user.
  				  Arg::Gds(isc_command_end_err2) << Arg::Num(1) << Arg::Num(1));
  	}
  
  	// Get rid of the trailing ";" if there is one.
  
  	for (const TEXT* p = text + textLength; p-- > text;)
  	{
  		if (*p != ' ')
  		{
  			if (*p == ';')
  				textLength = p - text;
  			break;
  		}
  	}
  
  	if (textLength > MAX_SQL_LENGTH)
  	{
  		ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-902) <<
  				  Arg::Gds(isc_imp_exc) <<
  				  Arg::Gds(isc_sql_too_long) << Arg::Num(MAX_SQL_LENGTH));
  	}
  
  	// allocate the statement block, then prepare the statement
  
  	MemoryPool* statementPool = database->createPool();
  	MemoryPool* scratchPool = NULL;
  	dsql_req* request = NULL;
  
  	Jrd::ContextPoolHolder statementContext(tdbb, statementPool);
  	try
  	{
  		DsqlCompiledStatement* statement = FB_NEW_POOL(*statementPool) DsqlCompiledStatement(*statementPool);
  
  		scratchPool = database->createPool();
  
  		DsqlCompilerScratch* scratch = FB_NEW_POOL(*scratchPool) DsqlCompilerScratch(*scratchPool, database,
  			transaction, statement);
  		scratch->clientDialect = clientDialect;
  
  		if (isInternalRequest)
  			scratch->flags |= DsqlCompilerScratch::FLAG_INTERNAL_REQUEST;
  
  		string transformedText;
  
  		{	// scope to delete parser before the scratch pool is gone
  			Jrd::ContextPoolHolder scratchContext(tdbb, scratchPool);
  
  			Parser parser(tdbb, *scratchPool, scratch, clientDialect,
  				scratch->getAttachment()->dbb_db_SQL_dialect, text, textLength,
  				tdbb->getAttachment()->att_charset);
  
  			// Parse the SQL statement.  If it croaks, return
  			request = parser.parse();
  			request->liveScratchPool = scratchPool;
  
  			if (parser.isStmtAmbiguous())
  				scratch->flags |= DsqlCompilerScratch::FLAG_AMBIGUOUS_STMT;
  
  			transformedText = parser.getTransformedString();
  		}
  
  		request->req_dbb = scratch->getAttachment();
  		request->req_transaction = scratch->getTransaction();
  		request->statement = scratch->getStatement();
  
  		SSHORT charSetId = database->dbb_attachment->att_charset;
  
  		// If the attachment charset is NONE, replace non-ASCII characters by question marks, so
  		// that engine internals doesn't receive non-mappeable data to UTF8. If an attachment
  		// charset is used, validate the string.
  		if (charSetId == CS_NONE)
  		{
  			for (char* p = transformedText.begin(), *end = transformedText.end(); p < end; ++p)
  			{
  				if (UCHAR(*p) > 0x7F)
  					*p = '?';
  			}
  		}
  		else
  		{
  			CharSet* charSet = INTL_charset_lookup(tdbb, charSetId);
  
  			if (!charSet->wellFormed(transformedText.length(),
  					(const UCHAR*) transformedText.begin(), NULL))
  			{
  				ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-104) <<
  						  Arg::Gds(isc_malformed_string));
  			}
  
  			UCharBuffer temp;
  
  			CsConvert conversor(charSet->getStruct(),
  				INTL_charset_lookup(tdbb, CS_METADATA)->getStruct());
  			conversor.convert(transformedText.length(), (const UCHAR*) transformedText.c_str(), temp);
  
  			transformedText.assign(temp.begin(), temp.getCount());
  		}
  
  		statement->setSqlText(FB_NEW_POOL(*statementPool) RefString(*statementPool, transformedText));
  
  		// allocate the send and receive messages
  
  		statement->setSendMsg(FB_NEW_POOL(*statementPool) dsql_msg(*statementPool));
  		dsql_msg* message = FB_NEW_POOL(*statementPool) dsql_msg(*statementPool);
  		statement->setReceiveMsg(message);
  		message->msg_number = 1;
  
  		statement->setType(DsqlCompiledStatement::TYPE_SELECT);
  
  		request->req_traced = true;
  		trace.setStatement(request);
  
  		ntrace_result_t traceResult = ITracePlugin::RESULT_SUCCESS;
  		try
  		{
  			bool destroyScratchPool = false;
  			request->dsqlPass(tdbb, scratch, &destroyScratchPool, &traceResult);
  
  			if (destroyScratchPool)
  			{
  				database->deletePool(scratchPool);
  				request->liveScratchPool = scratchPool = NULL;
  			}
  		}
  		catch (const Exception&)
  		{
  			trace.prepare(traceResult);
  			throw;
  		}
  		trace.prepare(traceResult);
  
  		return request;
  	}
  	catch (const Firebird::Exception&)
  	{
  		trace.prepare(ITracePlugin::RESULT_FAILED);
  
  		if (request)
  		{
  			request->req_traced = false;
  			dsql_req::destroy(tdbb, request, true);
  		}
  		else
  		{
  			database->deletePool(scratchPool);
  			database->deletePool(statementPool);
  		}
  
  		throw;
  	}
  }
  
  
  /**
  
   	put_item
  
      @brief	Put information item in output buffer if there is room, and
   	return an updated pointer.  If there isn't room for the item,
   	indicate truncation and return NULL.
  
  
      @param item
      @param length
      @param string
      @param ptr
      @param end
  
   **/
  static UCHAR* put_item(	UCHAR	item,
  						const USHORT	length,
  						const UCHAR* string,
  						UCHAR*	ptr,
  						const UCHAR* const end)
  {
  	if (ptr + length + 3 >= end)
  	{
  		*ptr = isc_info_truncated;
  		return NULL;
  	}
  
  	*ptr++ = item;
  
  	*ptr++ = (UCHAR) length;
  	*ptr++ = length >> 8;
  
  	if (length)
  		memcpy(ptr, string, length);
  
  	return ptr + length;
  }
  
  
  // Release a compiled statement.
  static void release_statement(DsqlCompiledStatement* statement)
  {
  	if (statement->getParentRequest())
  	{
  		dsql_req* parent = statement->getParentRequest();
  
  		FB_SIZE_T pos;
  		if (parent->cursors.find(statement, pos))
  			parent->cursors.remove(pos);
  
  		statement->setParentRequest(NULL);
  	}
  
  	statement->setSqlText(NULL);
  }
  
  
  dsql_req::dsql_req(MemoryPool& pool)
  	: req_pool(pool),
  	  statement(NULL),
  	  liveScratchPool(NULL),
  	  cursors(req_pool),
  	  req_dbb(NULL),
  	  req_transaction(NULL),
  	  req_msg_buffers(req_pool),
  	  req_cursor_name(req_pool),
  	  req_cursor(NULL),
  	  req_batch(NULL),
  	  req_user_descs(req_pool),
  	  req_traced(false),
  	  req_timeout(0)
  {
  }
  
  dsql_req::~dsql_req()
  {
  }
  
  void dsql_req::setCursor(thread_db* /*tdbb*/, const TEXT* /*name*/)
  {
  	status_exception::raise(
  		Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
  		Arg::Gds(isc_dsql_sqlda_err) <<
  		Arg::Gds(isc_req_sync));
  }
  
  void dsql_req::setDelayedFormat(thread_db* /*tdbb*/, Firebird::IMessageMetadata* /*metadata*/)
  {
  	status_exception::raise(
  		Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
  		Arg::Gds(isc_dsql_sqlda_err) <<
  		Arg::Gds(isc_req_sync));
  }
  
  bool dsql_req::fetch(thread_db* /*tdbb*/, UCHAR* /*msgBuffer*/)
  {
  	status_exception::raise(
  		Arg::Gds(isc_sqlerr) << Arg::Num(-804) <<
  		Arg::Gds(isc_dsql_sqlda_err) <<
  		Arg::Gds(isc_req_sync));
  
  	return false;	// avoid warning
  }
  
  unsigned int dsql_req::getTimeout()
  {
  	return req_timeout;
  }
  
  unsigned int dsql_req::getActualTimeout()
  {
  	if (req_timer)
  		return req_timer->getValue();
  
  	return 0;
  }
  
  void dsql_req::setTimeout(unsigned int timeOut)
  {
  	req_timeout = timeOut;
  }
  
  TimeoutTimer* dsql_req::setupTimer(thread_db* tdbb)
  {
  	if (statement->getFlags() & JrdStatement::FLAG_INTERNAL)
  		return req_timer;
  
  	if (req_request)
  	{
  		req_request->req_timeout = this->req_timeout;
  
  		fb_assert(!req_request->req_caller);
  		if (req_request->req_caller)
  		{
  			if (req_timer)
  				req_timer->setup(0, 0);
  			return req_timer;
  		}
  	}
  
  	Database* dbb = tdbb->getDatabase();
  	Attachment* att = tdbb->getAttachment();
  
  	ISC_STATUS toutErr = isc_cfg_stmt_timeout;
  	unsigned int timeOut = dbb->dbb_config->getStatementTimeout() * 1000;
  
  	if (req_timeout)
  	{
  		if (!timeOut || req_timeout < timeOut)
  		{
  			timeOut = req_timeout;
  			toutErr = isc_req_stmt_timeout;
  		}
  	}
  	else
  	{
  		const unsigned int attTout = att->getStatementTimeout();
  
  		if (!timeOut || attTout && attTout < timeOut)
  		{
  			timeOut = attTout;
  			toutErr = isc_att_stmt_timeout;
  		}
  	}
  
  	if (!req_timer && timeOut)
  	{
  		req_timer = FB_NEW TimeoutTimer();
  		req_request->req_timer = this->req_timer;
  	}
  
  	if (req_timer)
  	{
  		req_timer->setup(timeOut, toutErr);
  		req_timer->start();
  	}
  
  	return req_timer;
  }
  
  // Release a dynamic request.
  void dsql_req::destroy(thread_db* tdbb, dsql_req* request, bool drop)
  {
  	SET_TDBB(tdbb);
  
  	if (request->req_timer)
  	{
  		request->req_timer->stop();
  		request->req_timer = NULL;
  	}
  
  	// If request is parent, orphan the children and release a portion of their requests
  
  	for (FB_SIZE_T i = 0; i < request->cursors.getCount(); ++i)
  	{
  		DsqlCompiledStatement* child = request->cursors[i];
  		child->addFlags(DsqlCompiledStatement::FLAG_ORPHAN);
  		child->setParentRequest(NULL);
  
  		// hvlad: lines below is commented out as
  		// - child is already unlinked from its parent request
  		// - we should not free child's sql text until its owner request is alive
  		// It seems to me we should destroy owner request here, not a child
  		// statement - as it always was before
  
  		//Jrd::ContextPoolHolder context(tdbb, &child->getPool());
  		//release_statement(child);
  	}
  
  	// If the request had an open cursor, close it
  
  	if (request->req_cursor)
  		DsqlCursor::close(tdbb, request->req_cursor);
  
  	if (request->req_batch)
  	{
  		delete request->req_batch;
  		request->req_batch = nullptr;
  	}
  
  	Jrd::Attachment* att = request->req_dbb->dbb_attachment;
  	const bool need_trace_free = request->req_traced && TraceManager::need_dsql_free(att);
  	if (need_trace_free)
  	{
  		TraceSQLStatementImpl stmt(request, NULL);
  		TraceManager::event_dsql_free(att, &stmt, DSQL_drop);
  	}
  	request->req_traced = false;
  
  	if (request->req_cursor_name.hasData())
  	{
  		request->req_dbb->dbb_cursors.remove(request->req_cursor_name);
  		request->req_cursor_name = "";
  	}
  
  	// If a request has been compiled, release it now
  
  	if (request->req_request)
  	{
  		ThreadStatusGuard status_vector(tdbb);
  
  		try
  		{
  			CMP_release(tdbb, request->req_request);
  			request->req_request = NULL;
  		}
  		catch (Firebird::Exception&)
  		{} // no-op
  	}
  
  	const DsqlCompiledStatement* statement = request->getStatement();
  	release_statement(const_cast<DsqlCompiledStatement*>(statement));
  
  	// Release the entire request if explicitly asked for
  
  	if (drop)
  	{
  		request->req_dbb->deletePool(&request->getPool());
  		request->req_dbb->deletePool(request->liveScratchPool);
  	}
  }
  
  
  // Return as UTF8
  string IntlString::toUtf8(DsqlCompilerScratch* dsqlScratch) const
  {
  	CHARSET_ID id = CS_dynamic;
  
  	if (charset.hasData())
  	{
  		const dsql_intlsym* resolved = METD_get_charset(dsqlScratch->getTransaction(),
  			charset.length(), charset.c_str());
  
  		if (!resolved)
  		{
  			// character set name is not defined
  			ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-504) <<
  					  Arg::Gds(isc_charset_not_found) << charset);
  		}
  
  		id = resolved->intlsym_charset_id;
  	}
  
  	string utf;
  	return DataTypeUtil::convertToUTF8(s, utf, id, ERRD_post) ? utf : s;
  }
  
  
  /**
  
  	sql_info
  
  	@brief	Return DSQL information buffer.
  
  	@param request
  	@param item_length
  	@param items
  	@param info_length
  	@param info
  
   **/
  
  static void sql_info(thread_db* tdbb,
  					 dsql_req* request,
  					 ULONG item_length,
  					 const UCHAR* items,
  					 ULONG info_length,
  					 UCHAR* info)
  {
  	if (!item_length || !items || !info_length || !info)
  		return;
  
  	UCHAR buffer[BUFFER_SMALL];
  	memset(buffer, 0, sizeof(buffer));
  
  	// Pre-initialize buffer. This is necessary because we don't want to transfer rubbish over the wire
  	memset(info, 0, info_length);
  
  	const UCHAR* const end_items = items + item_length;
  	const UCHAR* const end_info = info + info_length;
  	UCHAR *start_info;
  
  	if (*items == isc_info_length)
  	{
  		start_info = info;
  		items++;
  	}
  	else
  		start_info = NULL;
  
  	// CVC: Is it the idea that this pointer remains with its previous value
  	// in the loop or should it be made NULL in each iteration?
  	const dsql_msg* message = NULL;
  	bool messageFound = false;
  	USHORT first_index = 0;
  
  	const DsqlCompiledStatement* statement = request->getStatement();
  
  	while (items < end_items && *items != isc_info_end)
  	{
  		ULONG length;
  		USHORT number;
  		ULONG value;
  		const UCHAR item = *items++;
  
  		switch (item)
  		{
  		case isc_info_sql_select:
  		case isc_info_sql_bind:
  			message = (item == isc_info_sql_select) ?
  				statement->getReceiveMsg() : statement->getSendMsg();
  			messageFound = true;
  			if (info + 1 >= end_info)
  			{
  				*info = isc_info_truncated;
  				return;
  			}
  			*info++ = item;
  			break;
  
  		case isc_info_sql_stmt_flags:
  			value = IStatement::FLAG_REPEAT_EXECUTE;
  			switch (statement->getType())
  			{
  			case DsqlCompiledStatement::TYPE_CREATE_DB:
  			case DsqlCompiledStatement::TYPE_DDL:
  				value &= ~IStatement::FLAG_REPEAT_EXECUTE;
  				break;
  			case DsqlCompiledStatement::TYPE_SELECT:
  			case DsqlCompiledStatement::TYPE_SELECT_UPD:
  			case DsqlCompiledStatement::TYPE_SELECT_BLOCK:
  				value |= IStatement::FLAG_HAS_CURSOR;
  				break;
  			}
  			length = put_vax_long(buffer, value);
  			info = put_item(item, length, buffer, info, end_info);
  			if (!info)
  				return;
  			break;
  
  		case isc_info_sql_stmt_type:
  			switch (statement->getType())
  			{
  			case DsqlCompiledStatement::TYPE_SELECT:
  				number = isc_info_sql_stmt_select;
  				break;
  			case DsqlCompiledStatement::TYPE_SELECT_UPD:
  				number = isc_info_sql_stmt_select_for_upd;
  				break;
  			case DsqlCompiledStatement::TYPE_CREATE_DB:
  			case DsqlCompiledStatement::TYPE_DDL:
  				number = isc_info_sql_stmt_ddl;
  				break;
  			case DsqlCompiledStatement::TYPE_COMMIT:
  			case DsqlCompiledStatement::TYPE_COMMIT_RETAIN:
  				number = isc_info_sql_stmt_commit;
  				break;
  			case DsqlCompiledStatement::TYPE_ROLLBACK:
  			case DsqlCompiledStatement::TYPE_ROLLBACK_RETAIN:
  				number = isc_info_sql_stmt_rollback;
  				break;
  			case DsqlCompiledStatement::TYPE_START_TRANS:
  				number = isc_info_sql_stmt_start_trans;
  				break;
  			case DsqlCompiledStatement::TYPE_SESSION_MANAGEMENT:
  				number = isc_info_sql_stmt_ddl;		// ?????????????????
  				break;
  			case DsqlCompiledStatement::TYPE_INSERT:
  				number = isc_info_sql_stmt_insert;
  				break;
  			case DsqlCompiledStatement::TYPE_UPDATE:
  			case DsqlCompiledStatement::TYPE_UPDATE_CURSOR:
  				number = isc_info_sql_stmt_update;
  				break;
  			case DsqlCompiledStatement::TYPE_DELETE:
  			case DsqlCompiledStatement::TYPE_DELETE_CURSOR:
  				number = isc_info_sql_stmt_delete;
  				break;
  			case DsqlCompiledStatement::TYPE_EXEC_PROCEDURE:
  				number = isc_info_sql_stmt_exec_procedure;
  				break;
  			case DsqlCompiledStatement::TYPE_SET_GENERATOR:
  				number = isc_info_sql_stmt_set_generator;
  				break;
  			case DsqlCompiledStatement::TYPE_SAVEPOINT:
  				number = isc_info_sql_stmt_savepoint;
  				break;
  			case DsqlCompiledStatement::TYPE_EXEC_BLOCK:
  				number = isc_info_sql_stmt_exec_procedure;
  				break;
  			case DsqlCompiledStatement::TYPE_SELECT_BLOCK:
  				number = isc_info_sql_stmt_select;
  				break;
  			default:
  				number = 0;
  				break;
  			}
  			length = put_vax_long(buffer, (SLONG) number);
  			info = put_item(item, length, buffer, info, end_info);
  			if (!info)
  				return;
  			break;
  
  		case isc_info_sql_sqlda_start:
  			length = *items++;
  			first_index = static_cast<USHORT>(gds__vax_integer(items, length));
  			items += length;
  			break;
  
  		case isc_info_sql_batch_fetch:
  			if (statement->getFlags() & DsqlCompiledStatement::FLAG_NO_BATCH)
  				number = 0;
  			else
  				number = 1;
  			length = put_vax_long(buffer, (SLONG) number);
  			if (!(info = put_item(item, length, buffer, info, end_info)))
  				return;
  			break;
  
  		case isc_info_sql_records:
  			length = get_request_info(tdbb, request, sizeof(buffer), buffer);
  			if (length && !(info = put_item(item, length, buffer, info, end_info)))
  				return;
  			break;
  
  		case isc_info_sql_stmt_timeout_user:
  		case isc_info_sql_stmt_timeout_run:
  			value = (item == isc_info_sql_stmt_timeout_user) ? request->getTimeout() : request->getActualTimeout();
  
  			length = put_vax_long(buffer, value);
  			if (!(info = put_item(item, length, buffer, info, end_info)))
  				return;
  			break;
  
  		case isc_info_sql_stmt_blob_align:
  			value = DsqlBatch::BLOB_STREAM_ALIGN;
  
  			length = put_vax_long(buffer, value);
  			if (!(info = put_item(item, length, buffer, info, end_info)))
  				return;
  			break;
  
  		case isc_info_sql_get_plan:
  		case isc_info_sql_explain_plan:
  			{
  				const bool detailed = (item == isc_info_sql_explain_plan);
  				string plan = OPT_get_plan(tdbb, request->req_request, detailed);
  
  				if (plan.hasData())
  				{
  					// 1-byte item + 2-byte length + isc_info_end/isc_info_truncated == 4
  					const ULONG buffer_length = end_info - info - 4;
  					const ULONG max_length = MIN(buffer_length, MAX_USHORT);
  
  					if (plan.length() > max_length)
  					{
  						// If the plan doesn't fit the supplied buffer or exceeds the API limits,
  						// truncate it to the rightmost space and add ellipsis to the end
  						plan.resize(max_length);
  
  						while (plan.length() > max_length - 4)
  						{
  							const FB_SIZE_T pos = plan.find_last_of(' ');
  							if (pos == string::npos)
  								break;
  							plan.resize(pos);
  						}
  
  						plan += " ...";
  
  						if (plan.length() <= max_length)
  						{
  							info = put_item(item, plan.length(), reinterpret_cast<const UCHAR*>(plan.c_str()),
  											info, end_info);
  						}
  
  						*info = isc_info_truncated;
  						info = NULL;
  					}
  					else
  					{
  						info = put_item(item, plan.length(), reinterpret_cast<const UCHAR*>(plan.c_str()),
  										info, end_info);
  					}
  				}
  
  				if (!info)
  					return;
  			}
  			break;
  
  		case isc_info_sql_num_variables:
  		case isc_info_sql_describe_vars:
  			if (messageFound)
  			{
  				number = message ? message->msg_index : 0;
  				length = put_vax_long(buffer, (SLONG) number);
  				if (!(info = put_item(item, length, buffer, info, end_info)))
  					return;
  				if (item == isc_info_sql_num_variables)
  					continue;
  
  				const UCHAR* end_describe = items;
  				while (end_describe < end_items &&
  					*end_describe != isc_info_end && *end_describe != isc_info_sql_describe_end)
  				{
  					end_describe++;
  				}
  
  				info = var_info(message, items, end_describe, info, end_info, first_index,
  					message == statement->getSendMsg());
  				if (!info)
  					return;
  
  				items = end_describe;
  				if (*items == isc_info_sql_describe_end)
  					items++;
  				break;
  			}
  			// else fall into
  
  		default:
  			buffer[0] = item;
  			length = 1 + put_vax_long(buffer + 1, (SLONG) isc_infunk);
  			if (!(info = put_item(isc_info_error, length, buffer, info, end_info)))
  				return;
  		}
  	}
  
  	*info++ = isc_info_end;
  
  	if (start_info && (end_info - info >= 7))
  	{
  		const SLONG number = info - start_info;
  		fb_assert(number > 0);
  		memmove(start_info + 7, start_info, number);
  		USHORT length = put_vax_long(buffer, number);
  		fb_assert(length == 4); // We only accept SLONG
  		put_item(isc_info_length, length, buffer, start_info, end_info);
  	}
  }
  
  
  /**
  
   	var_info
  
      @brief	Provide information on an internal message.
  
  
      @param message
      @param items
      @param end_describe
      @param info
      @param end
      @param first_index
  
   **/
  static UCHAR* var_info(const dsql_msg* message,
  					   const UCHAR* items,
  					   const UCHAR* const end_describe,
  					   UCHAR* info,
  					   const UCHAR* const end,
  					   USHORT first_index,
  					   bool input_message)
  {
  	if (!message || !message->msg_index)
  		return info;
  
  	thread_db* tdbb = JRD_get_thread_data();
  	Jrd::Attachment* attachment = tdbb->getAttachment();
  
  	HalfStaticArray<const dsql_par*, 16> parameters;
  
  	for (FB_SIZE_T i = 0; i < message->msg_parameters.getCount(); ++i)
  	{
  		const dsql_par* param = message->msg_parameters[i];
  
  		if (param->par_index)
  		{
  			if (param->par_index > parameters.getCount())
  				parameters.grow(param->par_index);
  			fb_assert(!parameters[param->par_index - 1]);
  			parameters[param->par_index - 1] = param;
  		}
  	}
  
  	UCHAR buf[128];
  
  	for (FB_SIZE_T i = 0; i < parameters.getCount(); i++)
  	{
  		const dsql_par* param = parameters[i];
  		fb_assert(param);
  
  		if (param->par_index >= first_index)
  		{
  			SLONG sql_len, sql_sub_type, sql_scale, sql_type;
  			param->par_desc.getSqlInfo(&sql_len, &sql_sub_type, &sql_scale, &sql_type);
  
  			if (input_message &&
  				(param->par_desc.dsc_dtype == dtype_text || param->par_is_text) &&
  				(param->par_desc.dsc_flags & DSC_null))
  			{
  				sql_type = SQL_NULL;
  				sql_len = 0;
  				sql_sub_type = 0;
  			}
  			else if (param->par_desc.dsc_dtype == dtype_varying && param->par_is_text)
  				sql_type = SQL_TEXT;
  
  			if (sql_type && (param->par_desc.dsc_flags & DSC_nullable))
  				sql_type |= 0x1;
  
  			for (const UCHAR* describe = items; describe < end_describe;)
  			{
  				USHORT length;
  				MetaName name;
  				const UCHAR* buffer = buf;
  				UCHAR item = *describe++;
  
  				switch (item)
  				{
  				case isc_info_sql_sqlda_seq:
  					length = put_vax_long(buf, (SLONG) param->par_index);
  					break;
  
  				case isc_info_sql_message_seq:
  					length = 0;
  					break;
  
  				case isc_info_sql_type:
  					length = put_vax_long(buf, (SLONG) sql_type);
  					break;
  
  				case isc_info_sql_sub_type:
  					length = put_vax_long(buf, (SLONG) sql_sub_type);
  					break;
  
  				case isc_info_sql_scale:
  					length = put_vax_long(buf, (SLONG) sql_scale);
  					break;
  
  				case isc_info_sql_length:
  					length = put_vax_long(buf, (SLONG) sql_len);
  					break;
  
  				case isc_info_sql_null_ind:
  					length = put_vax_long(buf, (SLONG) (sql_type & 1));
  					break;
  
  				case isc_info_sql_field:
  					if (param->par_name.hasData())
  					{
  						name = attachment->nameToUserCharSet(tdbb, param->par_name);
  						length = name.length();
  						buffer = reinterpret_cast<const UCHAR*>(name.c_str());
  					}
  					else
  						length = 0;
  					break;
  
  				case isc_info_sql_relation:
  					if (param->par_rel_name.hasData())
  					{
  						name = attachment->nameToUserCharSet(tdbb, param->par_rel_name);
  						length = name.length();
  						buffer = reinterpret_cast<const UCHAR*>(name.c_str());
  					}
  					else
  						length = 0;
  					break;
  
  				case isc_info_sql_owner:
  					if (param->par_owner_name.hasData())
  					{
  						name = attachment->nameToUserCharSet(tdbb, param->par_owner_name);
  						length = name.length();
  						buffer = reinterpret_cast<const UCHAR*>(name.c_str());
  					}
  					else
  						length = 0;
  					break;
  
  				case isc_info_sql_relation_alias:
  					if (param->par_rel_alias.hasData())
  					{
  						name = attachment->nameToUserCharSet(tdbb, param->par_rel_alias);
  						length = name.length();
  						buffer = reinterpret_cast<const UCHAR*>(name.c_str());
  					}
  					else
  						length = 0;
  					break;
  
  				case isc_info_sql_alias:
  					if (param->par_alias.hasData())
  					{
  						name = attachment->nameToUserCharSet(tdbb, param->par_alias);
  						length = name.length();
  						buffer = reinterpret_cast<const UCHAR*>(name.c_str());
  					}
  					else
  						length = 0;
  					break;
  
  				default:
  					buf[0] = item;
  					item = isc_info_error;
  					length = 1 + put_vax_long(buf + 1, (SLONG) isc_infunk);
  					break;
  				}
  
  				if (!(info = put_item(item, length, buffer, info, end)))
  					return info;
  			}
  
  			if (info + 1 >= end)
  			{
  				*info = isc_info_truncated;
  				return NULL;
  			}
  			*info++ = isc_info_sql_describe_end;
  		} // if()
  	} // for()
  
  	return info;
  }
  