/*
 *	PROGRAM:	Interactive SQL utility
 *	MODULE:		isql.epp
 *	DESCRIPTION:	Main line routine
 *
 * 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): ______________________________________.
 *
  Revision 1.5  2000/11/18 16:49:24  fsg
  Increased PRINT_BUFFER_LENGTH to 2048 to show larger plans
  Fixed Bug #122563 in extract.e get_procedure_args
  Apparently this has to be done in show.e also,
  but that is for another day :-)

  2001/05/20  Neil McCalden  add planonly option
  2001.09.09  Claudio Valderrama: put double quotes around identifiers
	in dialect 3 only when needed. Solve mischievous declaration/invocation
	of IUTILS_copy_SQL_id that made no sense and caused pointer problems.
  2001/10/03  Neil McCalden  pick up Firebird version from database server
	and display it with client version when -z used.
  2001.10.09 Claudio Valderrama: try to disconnect gracefully in batch mode.
  2001.11.23 Claudio Valderrama: skip any number of -- comments but only at
	the beginning and ignore void statements like block comments followed by
	a semicolon.
  2002-02-24 Sean Leyne - Code Cleanup of old Win 3.1 port (WINDOWS_ONLY)
  2003-08-15 Fred Polizo, Jr. - Fixed print_item() to correctly print
  string types as their hex represention for CHARACTER SET OCTETS.
  2004-11-16 Damyan Ivanov - bail out on error in non-interactive mode.

*/

#include "firebird.h"
#include <stdio.h>
#include "../jrd/intl.h"
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#include <ctype.h>
#include <errno.h>
#include "../common/utils_proto.h"
#include "../common/classes/array.h"
#include "../common/classes/init.h"
#include "../common/classes/ClumpletWriter.h"
#include "../common/classes/TempFile.h"
#include "../common/classes/FpeControl.h"
#include "../common/classes/GenericMap.h"
#include "../common/os/path_utils.h"
#include "../common/StatusHolder.h"
#include "../common/Tokens.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif

#ifdef HAVE_EDITLINE_H
// This is a local file included in our distribution - but not always
// compiled into the system
#include "editline.h"
#endif

enum literal_string_type
{
	INIT_STR_FLAG			= 0,
	SINGLE_QUOTED_STRING	= 1,
	DOUBLE_QUOTED_STRING	= 2,
	NO_MORE_STRING			= 3,
	INCOMPLETE_STRING		= 4
};

#include "../common/classes/timestamp.h"

#if defined(WIN_NT)
#include <windows.h>
#endif
#include "../jrd/ibase.h"
#include "../isql/isql.h"
#include "../yvalve/perf.h"
#include "../jrd/license.h"
#include "../jrd/constants.h"
#include "../jrd/ods.h"
#include "../common/file_params.h"
#include "../common/classes/ImplementHelper.h"
#include "../isql/extra_proto.h"
#include "../isql/isql_proto.h"
#include "../isql/show_proto.h"
#include "../isql/iutils_proto.h"
#include "../yvalve/perf_proto.h"
#include "../yvalve/utl_proto.h"
#include "../yvalve/why_proto.h"
#include "../common/gdsassert.h"

#include "../isql/Extender.h"
#include "../isql/PtrSentry.h"
#include "../common/classes/UserBlob.h"
#include "../common/classes/MsgPrint.h"

using Firebird::PathName;
using Firebird::TempFile;
using MsgFormat::SafeArg;

#include "../isql/ColList.h"
#include "../isql/InputDevices.h"
#include "../isql/OptionsBase.h"

#include "../common/classes/Switches.h"
#include "../isql/isqlswi.h"

#include "../intl/charsets.h"
#include <unicode/utf8.h>


//DATABASE DB = COMPILETIME "yachts.lnk";
DATABASE DB = COMPILETIME "yachts.lnk" RUNTIME isqlGlob.global_Db_name;

IsqlGlobals isqlGlob;

#define DIGIT(c)		((c) >= '0' && (c) <= '9')
#define INT64_LIMIT	 ((((SINT64) 1) << 62) / 5)	// same as in cvt.cpp

//  Print lengths of numeric values

const size_t MAXCHARSET_SIZE	= 32;	// CHARSET names
const int SHORT_LEN		= 7;	// NUMERIC (4,2) = -327.68
const int LONG_LEN		= 12;	// NUMERIC (9,2) = -21474836.48
const int INT64_LEN		= 21;	// NUMERIC(18,2) = -92233720368547758.08
//const int QUAD_LEN		= 19;
const int FLOAT_LEN		= 14;	// -1.2345678E+38
const int DOUBLE_LEN		= 23;	// -1.234567890123456E+300
const int DATE_LEN		= 11;	// 11 for date only
const int DATETIME_LEN		= 25;	// 25 for date-time
const int TIME_ONLY_LEN		= 13;	// 13 for time only
const int DATE_ONLY_LEN		= 11;
const int BOOLEAN_LEN		= 7;	// <false>
const int UNKNOWN_LEN		= 20;	// Unknown type: %d

const int MAX_TERMS		= 10;	// max # of terms in an interactive cmd

const char* ISQL_COUNTERS_SET = "CurrentMemory, MaxMemory, RealTime, UserTime, Buffers, Reads, Writes, Fetches";
const int ISQL_COUNTERS = 8;

const char* UNKNOWN = "*unknown*";

namespace IcuUtil
{
	// Return the number of characters of a string.
	static unsigned charLength(SSHORT charset, unsigned len, const char* str)
	{
		charset = TTYPE_TO_CHARSET(charset);

		if (charset != CS_UNICODE_FSS && charset != CS_UTF8)
			return len;

		unsigned charLen = 0;
		unsigned i = 0;

		while (i < len)
		{
			UChar32 c;
			FB_U8_NEXT_UNSAFE(str, i, c);
			++charLen;
		}

		return charLen;
	}

	// Pads a string to a specified column width.
	static void pad(char* buffer, SSHORT charset, unsigned len, const char* str, unsigned width,
		bool right)
	{
		charset = TTYPE_TO_CHARSET(charset);

		if (charset != CS_UNICODE_FSS && charset != CS_UTF8)
		{
			// Truncate if necessary.
			if (len > width)
				len = width;

			if (right)
			{
				memcpy(buffer + width - len, str, len);
				if (width > len)
					memset(buffer, ' ', width - len);
			}
			else
			{
				memcpy(buffer, str, len);
				if (width > len)
					memset(buffer + len, ' ', width - len);
			}
			buffer[width] = '\0';
			return;
		}

		unsigned i = 0;

		while (i < len && width > 0)
		{
			UChar32 c;
			FB_U8_NEXT_UNSAFE(str, i, c);
			--width;
		}

		if (right)
		{
			while (width-- > 0)
				*buffer++ = ' ';
		}

		memcpy(buffer, str, i);
		buffer += i;

		if (!right)
		{
			while (width-- > 0)
				*buffer++ = ' ';
		}

		*buffer = '\0';
	}
}

static inline bool commit_trans(Firebird::ITransaction** x)
{
	(*x)->commit(fbStatus);
	if (ISQL_errmsg (fbStatus))
	{
		(*x)->rollback(fbStatus);
		if (!(fbStatus->getState() & Firebird::IStatus::STATE_ERRORS))
		{
			*x = NULL;
		}

		return false;
	}

	*x = NULL;
	return true;
}

static inline int fb_isspace(const char c)
{
	return isspace((int)(UCHAR)c);
}

static inline int fb_isspace(const SSHORT c)
{
	return isspace((int)(UCHAR)c);
}

static inline int fb_isdigit(const char c)
{
	return isdigit((int)(UCHAR)c);
}


// I s q l G l o b a l s : : p r i n t f
// Output to the Out stream.
void IsqlGlobals::printf(const char* buffer, ...)
{
	va_list args;
	va_start(args, buffer);
	vfprintf(Out, buffer, args);
	va_end(args);
	fflush(Out); // John's fix.
}

// I s q l G l o b a l s : : p r i n t s
// Output to the Out stream a literal string. No escape characters recognized.
void IsqlGlobals::prints(const char* buffer)
{
	fprintf(Out, "%s", buffer);
	fflush(Out); // John's fix.
}



struct ri_actions
{
	const SCHAR* ri_action_name;
	const SCHAR* ri_action_print_caps;
	const SCHAR* ri_action_print_mixed;
};

static processing_state add_row(TEXT*);
static processing_state blobedit(const TEXT*, const TEXT* const*);
static processing_state bulk_insert_hack(const char* command);
static bool bulk_insert_retriever(const char* prompt);
static bool check_date(const tm& times);
static bool check_time(const tm& times);
static bool check_timestamp(const tm& times, const int msec);
static size_t chop_at(char target[], const size_t size);
static void col_check(const TEXT*, unsigned*);
static processing_state copy_table(TEXT*, TEXT*, TEXT*);
static processing_state create_db(const TEXT*, TEXT*);
static void do_isql();
static processing_state drop_db();
static processing_state edit(const TEXT* const*);
static processing_state end_trans();
static processing_state escape(const TEXT*);
static processing_state frontend(const TEXT*);
static processing_state frontend_set(const char* cmd, const char* const* parms,
	const char* const* lparms, char* const bad_dialect_buf, bool& bad_dialect);
static void frontend_free_parms(TEXT*[], TEXT*[], TEXT parm_defaults[][1]);
static void frontend_load_parms(const TEXT* p, TEXT* parms[], TEXT* lparms[],
	TEXT parm_defaults[][1]);
static processing_state do_set_command(const TEXT*, bool*);
static processing_state get_dialect(const char* const dialect_str,
	char* const bad_dialect_buf, bool& bad_dialect);
static processing_state get_statement(Firebird::string&, const TEXT*);
static bool get_numeric(const UCHAR*, USHORT, SSHORT*, SINT64*);
static void print_set(const char* str, bool v);
static processing_state print_sets();
static processing_state help(const TEXT*);
static bool isyesno(const TEXT*);
static processing_state newdb(TEXT*, const TEXT*, const TEXT*, int, const TEXT*, bool);
static processing_state newinput(const TEXT*);
static processing_state newoutput(const TEXT*);
static processing_state newsize(const TEXT*, const TEXT*);
static processing_state newMaxRows(const TEXT* newMaxRowsStr);
static processing_state newtrans(const TEXT*);
static processing_state parse_arg(int, SCHAR**, SCHAR*); //, FILE**);
#ifdef DEV_BUILD
static processing_state passthrough(const char* cmd);
#endif
static unsigned print_item(TEXT**, const IsqlVar*, const unsigned);
static void print_item_numeric(SINT64, int, int, TEXT*);
static processing_state print_line(Firebird::IMessageMetadata*, UCHAR*, const unsigned pad[], TEXT line[]);
static processing_state print_performance(const SINT64* perf_before);
static void print_message(Firebird::IMessageMetadata* msg, const char* dir);
static void process_header(Firebird::IMessageMetadata*, const unsigned pad[], TEXT header[], TEXT header2[]);
static void process_plan();
static SINT64 process_record_count(const int statement_type);
static int process_request_type();
static unsigned process_message_display(Firebird::IMessageMetadata* msg, unsigned pad[]);
static processing_state process_statement(const TEXT*);
#ifdef WIN_NT
static BOOL CALLBACK query_abort(DWORD);
#else
static int query_abort(const int, const int, void*);
#endif
static bool stdin_redirected();
static void strip_quotes(const TEXT*, TEXT*);
static const char* sqltype_to_string(unsigned);
static const char* charset_to_string(unsigned);

// The dialect spoken by the database, should be 0 when no database is connected.
USHORT global_dialect_spoken = 0;
USHORT requested_SQL_dialect = SQL_DIALECT_V6;
//bool connecting_to_pre_v6_server = false; Not used now.
bool Quiet = false;
#ifdef TRUSTED_AUTH
bool Trusted_auth = false;
#endif
bool Version_info = false;

// Utility transaction handle
static Firebird::ITransaction* D__trans = NULL;
static Firebird::ITransaction* M__trans = NULL;
static int global_numbufs;	// # of cache buffers on connect
static Firebird::IStatement* global_Stmt = NULL;
static SCHAR Password[128];
static SCHAR Charset[128];
static bool Merge_stderr;

static Firebird::GlobalPtr<Firebird::UCharBuffer> global_Buffer;
static bool Abort_flag = false;
static bool Interrupt_flag = false;
static Firebird::GlobalPtr<InputDevices> Filelist;
static int Pagelength = 20;
static bool Nodbtriggers = false; // No database triggers
static int Exit_value = 0;
static bool Interactive = true;
static bool Input_file = false;

static const char* DEFAULT_DML_TRANS_SQL = "SET TRANSACTION";
static Firebird::GlobalPtr<Firebird::string> TranParams;

// Values used in the SET command.
// Initial options set exclusively from the command line are not included here.
class SetValues
{
public:
	SetValues()
	{
		//ColList global_Cols;
		global_Col_default = 0; // Need to write code for it in the future.
		Echo = false;
		Time_display = false;
		Sqlda_display = false;
		Stats = false;
		Autocommit = true;	// Commit ddl
		Warnings = true;	// Print warnings
		Doblob = 1;			// Default to printing only text types
		List = false;
		Docount = false;
		maxRows = 0;
		Plan = false;
		Planonly = false;
		ExplainPlan = false;
		Heading = true;
		BailOnError = false;
		ISQL_charset[0] = 0;
		KeepTranParams = false;
	}

	ColList global_Cols;
	int global_Col_default;	// Need to write code for it in the future.
	bool Echo;
	bool Time_display;
	bool Sqlda_display;
	bool Stats;
	bool Autocommit;	// Commit ddl
	bool Warnings;		// Print warnings
	int Doblob;			// Default to printing only text types
	bool List;
	bool Docount;
	size_t maxRows;
	bool Plan;
	bool Planonly;
	bool ExplainPlan;
	bool Heading;
	bool BailOnError;
	SCHAR ISQL_charset[MAXCHARSET_SIZE];
	bool KeepTranParams;
};

static SetValues setValues;


static bool Merge_diagnostic = false;
static FILE* Diag;
static FILE* Help;

static const TEXT* const sql_prompt = "SQL> ";

// Keep in sync with the chars that have their own "case" in get_statement(...).
static const char FORBIDDEN_TERM_CHARS[] = { '\n', '-', '*', '/', SINGLE_QUOTE, DBL_QUOTE };
static const char FORBIDDEN_TERM_CHARS_DISPLAY[] = "<ENTER>, -, *, /, SINGLE_QUOTE, DOUBLE_QUOTE";

static bool global_psw = false;
static bool global_usr = false;
static bool global_role = false;
static bool has_global_numbufs = false;
static bool have_trans = false; // translation of word "Yes"
static TEXT yesword[BUFFER_LENGTH128];

// Didn't replace it by FB_SHORT_MONTHS because these are uppercased.
static const SCHAR* alpha_months[] =
{
	"JAN",
	"FEB",
	"MAR",
	"APR",
	"MAY",
	"JUN",
	"JUL",
	"AUG",
	"SEP",
	"OCT",
	"NOV",
	"DEC"
};

static UCHAR predefined_blob_subtype_bpb[] =
{
	isc_bpb_version1,
	isc_bpb_source_type, 1, 0,
	isc_bpb_target_type, 1, isc_blob_text
};

// No check on input argument for now.
inline void set_bpb_for_translation(const unsigned int blob_sub_type)
{
	predefined_blob_subtype_bpb[3] = (UCHAR) blob_sub_type;
}

// Note that these transaction options aren't understood in Version 3.3
static const UCHAR default_tpb[] =
{
	isc_tpb_version1, isc_tpb_write,
	isc_tpb_read_committed, isc_tpb_wait,
	isc_tpb_no_rec_version
};

#ifdef NOT_USED_OR_REPLACED
// CVC: Just in case we need it for R/O operations in the future.
static const UCHAR	batch_tpb[] =
{
	isc_tpb_version3, isc_tpb_read,
	isc_tpb_read_committed, isc_tpb_nowait,
	isc_tpb_rec_version
};
#endif

// If the action is restrict, do not print anything at all
static const ri_actions ri_actions_all[] =
{
	{RI_ACTION_CASCADE, RI_ACTION_CASCADE, "Cascade"},
	{RI_ACTION_NULL, RI_ACTION_NULL, "Set Null"},
	{RI_ACTION_DEFAULT, RI_ACTION_DEFAULT, "Set Default"},
	{RI_ACTION_NONE, RI_ACTION_NONE, "No Action"},
	{RI_RESTRICT, "", ""},
	{"", "", ""},
	{0, 0, 0}
};


static bool startTransaction(Firebird::ITransaction** t, unsigned len = 0, const UCHAR* tpb = NULL)
{
	if (DB && !*t)
	{
		*t = DB->startTransaction(fbStatus, len, tpb);
		if (ISQL_errmsg(fbStatus))
			return false;
	}

	return DB != NULL;
}

static bool M_Transaction()
{
	if (DB && !M__trans && setValues.KeepTranParams)
	{
		M__trans = DB->execute(fbStatus, NULL,
							   TranParams->length(), TranParams->c_str(),
							   isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);

		if (ISQL_errmsg(fbStatus))
			return false;

		return DB != NULL;
	}
	return startTransaction(&M__trans);
}

static bool D_Transaction()
{
	return startTransaction(&D__trans, sizeof(default_tpb), default_tpb);
}

// Transaction for all frontend commands
static bool frontendTransaction()
{
	return startTransaction(&fbTrans);
}

static void atexit_fb_shutdown()
{
	fb_shutdown(0, fb_shutrsn_app_stopped);
}

int CLIB_ROUTINE main(int argc, char* argv[])
{
/**************************************
 *
 *	m a i n
 *
 **************************************
 *
 * Functional description
 *	This calls ISQL_main, and exists to
 *	isolate main which does not exist under
 *	MS Windows.
 *
 **************************************/

	return ISQL_main(argc, argv);
}


int ISQL_main(int argc, char* argv[])
{
/**************************************
 *
 *	I S Q L _ m a i n
 *
 **************************************
 *
 * Functional description
 *	Choose between reading and executing or generating SQL
 *	ISQL_main isolates this from main for PC Clients.
 *
 **************************************/

#if defined(HAVE_EDITLINE_H) && defined(HAVE_LOCALE_H)
	setlocale(LC_CTYPE, "");
#endif
	atexit(&atexit_fb_shutdown);

	TEXT tabname[WORDLENGTH];
	tabname[0] = '\0';

	// Initialize globals
	isqlGlob.major_ods = 0;
	isqlGlob.minor_ods = 0;
	isqlGlob.db_SQL_dialect = 0;
	isqlGlob.att_charset = 0;

	// Output goes to stdout by default
	isqlGlob.Out = stdout;
	isqlGlob.Errfp = stderr;

	const processing_state ret = parse_arg(argc, argv, tabname);

	// Can't do a simple assignment because parse_arg may set Interactive to false.
	if (stdin_redirected())
		Interactive = false;

	// Init the diagnostics and help files
	if (Merge_diagnostic)
	    Diag = isqlGlob.Out;
	else
		Diag = stdout;

	Help = stdout;

	if (Merge_stderr)
		isqlGlob.Errfp = isqlGlob.Out;

	IUTILS_make_upper(tabname);
	switch (ret)
	{
	case EXTRACT:
	case EXTRACTALL:
		if (*isqlGlob.global_Db_name)
		{
			Interactive = false; // "extract" option only can be called from command-line

			// Let's use user and password if provided.
			// This should solve bug #112263 FSG 28.Jan.2001
			if (newdb(isqlGlob.global_Db_name, isqlGlob.User, Password, global_numbufs,
				isqlGlob.Role, false) == SKIP)
			{
				LegacyTables flag = ret == EXTRACT ? SQL_objects : ALL_objects;
				Exit_value = EXTRACT_ddl(flag, tabname);
				ISQL_disconnect_database(true);
			}
			else
				Exit_value = FINI_ERROR;
		}
		break;

	case ps_ERR:
	    {
			TEXT helpstring[158];
			IUTILS_msg_get(USAGE, sizeof(helpstring), helpstring);
			STDERROUT(helpstring);
			for (int i = 0; i < FB_NELEM(isql_in_sw_table); i++)
			{
				if (isql_in_sw_table[i].in_sw_msg > 0)
				{
					IUTILS_msg_get(isql_in_sw_table[i].in_sw_msg, sizeof(helpstring), helpstring);
					STDERROUT(helpstring);
				}
			}
			Exit_value = FINI_ERROR;
			break;
		}

	default:
		do_isql();
		// keep Exit_value to whatever it is set
		// by do_isql()
		// Exit_value = FINI_OK;
		break;
	}
#ifdef DEBUG_GDS_ALLOC
	// As ISQL can run under windows, all memory should be freed before
	// returning.  In debug mode this call will report unfreed blocks.

	//gds_alloc_report(0 ALLOC_ARGS);
	char fn[] = __FILE__;
	fn[strlen(fn) - 8] = 0; // all isql files in gen dir
	gds_alloc_report(0, fn, 0);
#endif

	return Exit_value;
}


void ISQL_array_dimensions(const TEXT* fieldname)
{
/**************************************
 *
 *	I S Q L _ a r r a y _ d i m e n s i o n s
 *
 **************************************
 *
 * Functional description
 *	Retrieves the dimensions of arrays and prints them.
 *
 *	Parameters:  fieldname -- the actual name of the array field
 *
 **************************************/

	isqlGlob.printf("[");

	if (!frontendTransaction())
		return;

	FOR FDIM IN RDB$FIELD_DIMENSIONS WITH
	   FDIM.RDB$FIELD_NAME EQ fieldname
	   SORTED BY FDIM.RDB$DIMENSION

		// Format is [lower:upper, lower:upper,..]
		// When lower == 1 no need to print a range. Done.
		// When upper == 1 no need to print a range either, but it's confusing. Not done.

		if (FDIM.RDB$DIMENSION > 0) {
			isqlGlob.printf(", ");
		}
		if (FDIM.RDB$LOWER_BOUND == 1)
			isqlGlob.printf("%ld", FDIM.RDB$UPPER_BOUND);
		else
			isqlGlob.printf("%ld:%ld", FDIM.RDB$LOWER_BOUND, FDIM.RDB$UPPER_BOUND);

	END_FOR
	ON_ERROR
		ISQL_errmsg(fbStatus);
		return;
	END_ERROR;

	isqlGlob.printf("]");
}



bool ISQL_dbcheck()
{
/**************************************
 *
 *	I S Q L _ d b c h e c k
 *
 **************************************
 *
 * Functional description
 *	Check to see if we are connected to a database.
 *  	Return true if connected, false otherwise
 *
 * Change from miroslavp on 2005.04.25
 *   Value assigned here on Exit_value is not used anywhere in the source
 *   and cause isql.exe to return 1 in Quiet mode even there is no error.
 *   If there is error Exit_value is set by callers - so it is removed from here.
 **************************************/
	TEXT errbuf[MSG_LENGTH];

	if (!(DB && isqlGlob.global_Db_name[0]))
	{
		if (!Quiet)
		{
			IUTILS_msg_get(NO_DB, errbuf);
			STDERROUT(errbuf);
		}
		return false;
	}

	return true;
}

static char userPrompt2[MSG_LENGTH];
static const char* const userPrompt = userPrompt2;
static char* lastInputLine = NULL;
static int getColumn = -1;



void ISQL_prompt(const TEXT* string)
{
/**************************************
 *
 *	I S Q L _ p r o m p t
 *
 **************************************
 *
 * Functional description
 *	Print a prompt string for interactive user
 *	Not for Windows, otherwise flush the string
 **************************************/
	//userPrompt = (const char*) string;
	fb_utils::copy_terminate(userPrompt2, string, MSG_LENGTH);

	//#ifndef HAVE_READLINE_READLINE_H
	//	fprintf(stdout, userPrompt.c_str());
	//	fflush(stdout);
	//#endif

}

static void readNextInputLine(const char* prompt)
{
/**************************************
 *
 *	r e a d N e x t I n p u t L i n e
 *
 **************************************
 *
 * Functional description
 *  Get next input line and put it in lastInputLine
 *  If the first read is EOF set lineInputLine to NULL
 *  Otherwise return the line up to EOF (excluded) or '\n' (included)
 * WARNING: If you bypass getNextInputChar() and call this directly,
 * remember to set getColumn = -1; immediately after calling this function
 * to avoid side effects or reading invalid memory locations.
 **************************************/

	if (lastInputLine != NULL)
	{
		free(lastInputLine);
		lastInputLine = NULL;
	}

	getColumn = 0;

#ifdef HAVE_EDITLINE_H
	if (Filelist->readingStdin())
	{
		// CVC: On 2005-04-02, use an empty prompt when not working in
		// interactive mode to behave like @@@ below at request by Pavel.
		const char* new_prompt = Interactive ? prompt : "";
		lastInputLine = readline(new_prompt);

		if (lastInputLine != NULL && strlen(lastInputLine) != 0) {
			add_history(lastInputLine);
		}
		// Let's count lines if someone wants to enable line number error messages
		// for console reading for whatever reason.
		++Filelist->Ifp().indev_aux;
		return;
	}

#endif
	// @@@ CVC: On 2005-04-02, take the "|| Echo" out at request by Pavel.
	if (Interactive && !Input_file)// || setValues.Echo)
	{
		// Write the prompt out.
		fprintf(stdout, "%s", prompt);
		fflush(stdout);
	}

	Firebird::string line;
	bool end = false;

	do
	{
		// Read the line
		char buffer[MAX_USHORT];

		if (fgets(buffer, sizeof(buffer), Filelist->Ifp().indev_fpointer) != NULL)
		{
			size_t lineSize = strlen(buffer);

			// If the last non empty line doesn't end in '\n', indev_aux won't be
			// updated, but then there're no more commands, so it's irrelevant.
			while (lineSize > 0 &&
				   (buffer[lineSize - 1] == '\n' || buffer[lineSize - 1] == '\r'))
			{
				buffer[--lineSize] = '\0';
				++Filelist->Ifp().indev_aux;
				end = true;
			}

			line.append(buffer, lineSize);
		}
		else if (line.isEmpty())
			return;
		else
			end = true;
	} while (!end);

	lastInputLine = (char*) malloc(line.length() + 1);
	memcpy(lastInputLine, line.c_str(), line.length() + 1);
}

static void readNextInputLine()
{
	readNextInputLine(userPrompt);
	if (setValues.Echo && (lastInputLine != NULL)) {
		isqlGlob.printf("%s%s", lastInputLine, NEWLINE);
	}
}

int getNextInputChar()
{
/**************************************
 *
 *	g e t N e x t I n p u t C h a r
 *
 **************************************
 *
 * Functional description
 *	Read next char from input
 *
 *
 **************************************/
	static int inputLen = 0;
	// At end of line try and read next line
	if (getColumn == -1)
	{
		readNextInputLine();
		if (lastInputLine)
			inputLen = (int) strlen(lastInputLine);
	}

	// readline found EOF
	if (lastInputLine == NULL) {
		return EOF;
	}

	// If at end of line return \n
	if (getColumn == inputLen) //(int) strlen(lastInputLine))
	{
		getColumn = -1;
		return '\n';
	}
    // cast to unsigned char to prevent sign expansion
    // this way we can distinguish russian ya (0xFF) and EOF (usually (-1))
	return (unsigned char)lastInputLine[getColumn++];
}


bool ISQL_errmsg(Firebird::IStatus* st)
{
/**************************************
 *
 *	I S Q L _ e r r m s g
 *
 **************************************
 *
 * Functional description
 *	Report error conditions
 *	Simulate isc_print_status exactly, to control stderr
 **************************************/
	TEXT errbuf[MSG_LENGTH];
	const ISC_STATUS* const status = st->getErrors();

	const unsigned state = st->getState();
	if (Quiet && (state & Firebird::IStatus::STATE_ERRORS))
		Exit_value = FINI_ERROR;
	//else
	{
		const ISC_STATUS* vec = status;
		if (vec[0] != isc_arg_gds)
			return false;

		if (!(state & Firebird::IStatus::STATE_ERRORS) &&
			(!(state & Firebird::IStatus::STATE_WARNINGS) || !setValues.Warnings))
		{
			return false;
		}

		if (state & Firebird::IStatus::STATE_ERRORS)
		{
			FB_SQLSTATE_STRING sqlstate;
			fb_sqlstate(sqlstate, status);
			IUTILS_msg_get(GEN_ERR, errbuf, SafeArg() << sqlstate);
			STDERROUT(errbuf);
			TEXT* err = errbuf;
			unsigned es = sizeof(errbuf);
			if (fb_interpret(err, es, &vec))
			{
				STDERROUT(errbuf);

				// Continuation of error
				*err++ = '-';
				--es;
				while (fb_interpret(err, es, &vec))
					STDERROUT(errbuf);
			}
		}

		ISQL_warning(fbStatus);

		if (Input_file)
		{
			int linenum = -1;
			if (status[0] == isc_arg_gds && status[1] == isc_dsql_error &&
				status[2] == isc_arg_gds && status[3] == isc_sqlerr && vec > &status[9])
			{
				switch (status[7])
				{
				case isc_dsql_field_err:
				case isc_dsql_procedure_err:
				case isc_dsql_relation_err:
				case isc_dsql_procedure_use_err:
				case isc_dsql_no_dup_name:
					vec = &status[8];
					while (*vec++ != isc_arg_end)
						if (vec[0] == isc_dsql_line_col_error && vec[1] == isc_arg_number)
						{
							linenum  = vec[2];
							//STDERROUT(s);
							break;
						}
					break;
				case isc_dsql_token_unk_err:
					if (status[8] == isc_arg_number)
					{
						linenum = status[9];
						//s = errbuf;
						//STDERROUT(s);
					}
					break;
				}
			}
			/* CVC: Obsolete on 2005.10.06 because now line & column are numeric arguments.
			if (s)
			{
				while (*s && !fb_isdigit(*s))
					++s;
				if (isdigit(*s))
				{
					linenum = 0;
					for (; *s && isdigit(*s); ++s)
						linenum = linenum * 10 + *s - '0';
				}
			}
			*/
			const InputDevices::indev& Ifp = Filelist->Ifp();
			if (linenum != -1)
			{
				linenum += Ifp.indev_line;
				IUTILS_msg_get(EXACTLINE, errbuf, SafeArg() << linenum << Ifp.fileName(true).c_str());
			}
			else
                IUTILS_msg_get(AFTERLINE, errbuf, SafeArg() << Ifp.indev_line << Ifp.fileName(true).c_str());

			STDERROUT(errbuf);
		}
	}

	return (state & Firebird::IStatus::STATE_ERRORS);
}


void ISQL_warning(Firebird::CheckStatusWrapper* st)
{
/**************************************
 *
 *	I S Q L _ w a r n i n g
 *
 **************************************
 *
 * Functional desription
 *	Report warning
 *	Simulate isc_print_status exactly, to control stderr
 **************************************/

	if ((st->getState() & Firebird::IStatus::STATE_WARNINGS) && setValues.Warnings)
	{
		const ISC_STATUS* vec = st->getWarnings();
		TEXT buf[MSG_LENGTH];
		if (fb_interpret(buf, sizeof(buf), &vec))
		{
			STDERROUT(buf);

			// Continuation of warning
			buf[0] = '-';
			while (fb_interpret(buf + 1, sizeof(buf) - 1, &vec)) {
				STDERROUT(buf);
			}
		}
	}

	st->init();
}


SSHORT ISQL_get_default_char_set_id()
{
/*************************************
*
*	I S Q L _ g e t _ d e f a u l t _ c h a r _ s e t _ i d
*
**************************************
*
* Functional description
*	Return the database default character set
*	id.
*
*	-1 if the value can not be determined.
*
**************************************/

/* What is the default character set for this database?
   There are three states:
   1.	There is no entry available in RDB$DATABASE
	Then - NONE
   2.   The entry in RDB$DATABASE does not exist in
	RDB$CHARACTER_SETS
	Then - -1 to cause all character set defs to show
   3.	An entry in RDB$CHARACTER_SETS
	Then - RDB$CHARACTER_SET_ID
*/
	SSHORT default_char_set_id = 0;
	FOR FIRST 1 EXT IN RDB$DATABASE
		WITH EXT.RDB$CHARACTER_SET_NAME NOT MISSING;

		default_char_set_id = -1;

		FOR FIRST 1 CHI IN RDB$CHARACTER_SETS
		WITH CHI.RDB$CHARACTER_SET_NAME = EXT.RDB$CHARACTER_SET_NAME

			default_char_set_id = CHI.RDB$CHARACTER_SET_ID;

		END_FOR;
	END_FOR;

	return (default_char_set_id);
}


SSHORT ISQL_get_field_length(const TEXT* field_name)
{
/**************************************
 *
 *	I S Q L _ g e t _ f i e l d _ l e n g t h
 *
 **************************************
 *
 *	Retrieve character or field length of character types.
 *
 **************************************/

	if (!frontendTransaction())
		return 0;

	SSHORT l = 0;
	FOR FLD IN RDB$FIELDS WITH
		FLD.RDB$FIELD_NAME EQ field_name

		if (FLD.RDB$CHARACTER_LENGTH.NULL)
			l = FLD.RDB$FIELD_LENGTH;
		else
			l = FLD.RDB$CHARACTER_LENGTH;
	END_FOR
	ON_ERROR
		ISQL_errmsg(fbStatus);
		return 0;
	END_ERROR;

	return l;
}


SSHORT ISQL_get_char_length(
	SSHORT fieldLength,
	SSHORT characterLengthNull, SSHORT characterLength,
	SSHORT characterSetIdNull, SSHORT characterSetId)
{
	if (characterLengthNull || characterLength == 0)
	{
		if (!characterSetIdNull)
		{
			FOR CS IN RDB$CHARACTER_SETS
				WITH CS.RDB$CHARACTER_SET_ID EQ characterSetId AND
					 CS.RDB$BYTES_PER_CHARACTER > 0
			{
				fieldLength /= CS.RDB$BYTES_PER_CHARACTER;
			}
			END_FOR
			ON_ERROR
				ISQL_errmsg(fbStatus);
			END_ERROR;
		}

		return fieldLength;
	}
	else
		return characterLength;
}


void ISQL_get_character_sets(SSHORT char_set_id, SSHORT collation, bool collate_only,
							 bool not_null, bool quote, TEXT* string)
{
/**************************************
 *
 *	I S Q L _ g e t _ c h a r a c t e r _ s e t s
 *
 **************************************
 *
 *	Retrieve character set and collation order and format it
 *
 **************************************/
#ifdef	DEV_BUILD
	bool found = false;
#endif

	const char* notNullStr = not_null ? " NOT NULL" : "";
	string[0] = 0;

	if (!frontendTransaction())
		return;

	//if (collation) {
	if (collation || collate_only)
	{
		FOR FIRST 1 COL IN RDB$COLLATIONS CROSS
			CST IN RDB$CHARACTER_SETS WITH
			COL.RDB$CHARACTER_SET_ID EQ CST.RDB$CHARACTER_SET_ID AND
			COL.RDB$COLLATION_ID EQ collation AND
			CST.RDB$CHARACTER_SET_ID EQ char_set_id
			SORTED BY COL.RDB$COLLATION_NAME, CST.RDB$CHARACTER_SET_NAME

#ifdef DEV_BUILD
			found = true;
#endif
			fb_utils::exact_name(CST.RDB$CHARACTER_SET_NAME);
			fb_utils::exact_name(COL.RDB$COLLATION_NAME);
			fb_utils::exact_name(CST.RDB$DEFAULT_COLLATE_NAME);

			char charSetName[QUOTEDLENGTH];
			char collateName[QUOTEDLENGTH];

			if (quote && isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION)
			{
				IUTILS_copy_SQL_id(CST.RDB$CHARACTER_SET_NAME, charSetName, DBL_QUOTE);
				IUTILS_copy_SQL_id(COL.RDB$COLLATION_NAME, collateName, DBL_QUOTE);
			}
			else
			{
				strcpy(charSetName, CST.RDB$CHARACTER_SET_NAME);
				strcpy(collateName, COL.RDB$COLLATION_NAME);
			}

			// Is specified collation the default collation for character set?
			if (strcmp (CST.RDB$DEFAULT_COLLATE_NAME, COL.RDB$COLLATION_NAME) == 0)
			{
				if (!collate_only)
					sprintf (string, " CHARACTER SET %s%s", charSetName, notNullStr);
			}
			else if (collate_only)
				sprintf (string, "%s COLLATE %s", notNullStr, collateName);
			else
				sprintf (string, " CHARACTER SET %s%s COLLATE %s",
						 charSetName, notNullStr, collateName);
		END_FOR
		ON_ERROR
			ISQL_errmsg(fbStatus);
			return;
		END_ERROR;
#ifdef DEV_BUILD
		if (!found)
		{
			TEXT Print_buffer[PRINT_BUFFER_LENGTH];
			sprintf(Print_buffer,
					"ISQL_get_character_set: charset %d collation %d not found.\n",
					char_set_id, collation);
			STDERROUT(Print_buffer);
		}
#endif
	}
	else
	{
		FOR FIRST 1 CST IN RDB$CHARACTER_SETS WITH
			CST.RDB$CHARACTER_SET_ID EQ char_set_id
			SORTED BY CST.RDB$CHARACTER_SET_NAME

#ifdef DEV_BUILD
			found = true;
#endif
			fb_utils::exact_name(CST.RDB$CHARACTER_SET_NAME);

			sprintf (string, " CHARACTER SET %s%s", CST.RDB$CHARACTER_SET_NAME, notNullStr);
		END_FOR
		ON_ERROR
			ISQL_errmsg(fbStatus);
			return;
		END_ERROR;
#ifdef DEV_BUILD
		if (!found)
		{
			TEXT Print_buffer[PRINT_BUFFER_LENGTH];
			sprintf(Print_buffer, "ISQL_get_character_set: charset %d not found.\n",
					char_set_id);
			STDERROUT(Print_buffer);
		}
#endif
	}
}


inline static bool failed()
{
	return (fbStatus->getState() & Firebird::IStatus::STATE_ERRORS);
}


inline static bool succeeded()
{
	return !failed();
}


processing_state ISQL_fill_var(IsqlVar* var,
							   Firebird::IMessageMetadata* msg,
							   unsigned index,
							   UCHAR* buf)
{
	var->field = msg->getField(fbStatus, index);		if (failed()) return ps_ERR;
	var->relation = msg->getRelation(fbStatus, index);	if (failed()) return ps_ERR;
	var->owner = msg->getOwner(fbStatus, index);		if (failed()) return ps_ERR;
	var->alias = msg->getAlias(fbStatus, index);		if (failed()) return ps_ERR;
	var->subType = msg->getSubType(fbStatus, index);	if (failed()) return ps_ERR;
	var->scale = msg->getScale(fbStatus, index);		if (failed()) return ps_ERR;
	var->type = msg->getType(fbStatus, index);			if (failed()) return ps_ERR;
	var->length = msg->getLength(fbStatus, index);		if (failed()) return ps_ERR;
	var->charSet = msg->getCharSet(fbStatus, index);	if (failed()) return ps_ERR;
	var->nullable = msg->isNullable(fbStatus, index);	if (failed()) return ps_ERR;

	if (buf)
	{
		var->nullInd = (short*) &buf[msg->getNullOffset(fbStatus, index)];	if (failed()) return ps_ERR;
		var->value.setPtr = &buf[msg->getOffset(fbStatus, index)];			if (failed()) return ps_ERR;
	}
	else
	{
		var->nullInd = 0;
		var->value.setPtr = 0;
	}

	return CONT;
}


// Check if statement ends with single-line comment.
bool ISQL_statement_ends_in_comment(const char* statement)
{
	const char* const statementStart = statement;
	const char* commentStart = NULL;
	const char* commentEnd = NULL;
	const char* altQuoteStart = NULL;
	char altQuoteChar = '\0';

	enum
	{
		normal,
		in_single_line_comment,
		in_block_comment,
		in_single_quoted_string,
		in_double_quoted_string
	} state = normal;

	while (char c = *statement++)
	{
		char lastChar = statement - 1 == statementStart ? '\0' : statement[-2];

		switch (c)
		{
			case '\n':
				if (state == in_single_line_comment)
					state = normal;
				break;

			case '-':
				// Could this the be start of a single-line comment.
				if (state == normal && lastChar == '-')
					state = in_single_line_comment;
				break;

			case '*':
				// Could this the be start of a comment. We can only look back, not forward.
				// Ignore possibilities of a comment beginning inside quoted strings.
				if (state == normal && lastChar == '/' && statement - commentEnd > 3)
				{
					state = in_block_comment;
					commentStart = statement - 2;
				}
				break;

			case '/':
				// Perhaps this is the end of a comment.
				// Ignore possibilities of a comment ending inside quoted strings.
				// Ignore things like /*/ since it isn't a block comment; only the start of it. Or end.
				if (state == in_block_comment && lastChar == '*' && statement - commentStart > 3)
				{
					state = normal;
					commentEnd = statement - 2;	// mark start of non-comment to track this: /**/*
				}
				break;

			case SINGLE_QUOTE:
				switch (state)
				{
					case normal:
						if (lastChar == 'q' || lastChar == 'Q')
						{
							altQuoteStart = statement - 2;

							if (!(altQuoteChar = *statement++))
								return false;

							switch (altQuoteChar)
							{
								case '{':
									altQuoteChar = '}';
									break;
								case '(':
									altQuoteChar = ')';
									break;
								case '[':
									altQuoteChar = ']';
									break;
								case '<':
									altQuoteChar = '>';
									break;
							}
						}
						else
							altQuoteChar = '\0';

						state = in_single_quoted_string;
						break;

					case in_single_quoted_string:
						if (!altQuoteChar || lastChar == altQuoteChar)
							state = normal;
						break;
				}
				break;

			case DBL_QUOTE:
				switch (state)
				{
					case normal:
						state = in_double_quoted_string;
						break;
					case in_double_quoted_string:
						state = normal;
						break;
				}
				break;

			default:
				break;
		}
	}

	return state == in_single_line_comment;
}


void ISQL_get_default_source(const TEXT* rel_name,
							 TEXT* field_name,
							 ISC_QUAD* blob_id)
{
/**************************************
 *
 *	I S Q L _ g e t _ d e f a u l t _ s o u r c e
 *
 **************************************
 *
 *	Retrieve the default source of a field.
 *	Relation_fields takes precedence over fields if both
 *	are present
 *
 *	For a domain, a NULL is passed for rel_name
 **************************************/

	fb_utils::exact_name(field_name);

	*blob_id = fbBlobNull;

	if (!frontendTransaction())
		return;

	if (rel_name)
	{
		// This is default for a column of a table
		FOR FLD IN RDB$FIELDS CROSS
			RFR IN RDB$RELATION_FIELDS WITH
			RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND
			RFR.RDB$RELATION_NAME EQ rel_name AND
			FLD.RDB$FIELD_NAME EQ field_name

			if (!RFR.RDB$DEFAULT_SOURCE.NULL)
				*blob_id = RFR.RDB$DEFAULT_SOURCE;
			else if (!FLD.RDB$DEFAULT_SOURCE.NULL)
				*blob_id = FLD.RDB$DEFAULT_SOURCE;

		END_FOR
		ON_ERROR
			ISQL_errmsg(fbStatus);
			return;
		END_ERROR;
	}
	else
	{
		// Default for a domain
		FOR FLD IN RDB$FIELDS WITH
			FLD.RDB$FIELD_NAME EQ field_name

			if (!FLD.RDB$DEFAULT_SOURCE.NULL)
				*blob_id = FLD.RDB$DEFAULT_SOURCE;

		END_FOR
		ON_ERROR
			ISQL_errmsg(fbStatus);
			return;
		END_ERROR;
	}
}


SLONG ISQL_get_index_segments(TEXT* segs,
								const size_t buf_size,
								const TEXT* indexname,
								bool delimited_yes)
{
/**************************************
 *
 *	I S Q L _ g e t _ i n d e x _ s e g m e n t s
 *
 **************************************
 *
 * Functional description
 *	returns the list of columns in an index.
 *
 **************************************/
	TEXT SQL_identifier[BUFFER_LENGTH128];

	*segs = '\0';

	if (!frontendTransaction())
		return 0;

	TEXT* const segs_end = segs + buf_size - 1;
	// Query to get column names
	SLONG n = 0;
	bool count_only = false;

	FOR SEG IN RDB$INDEX_SEGMENTS WITH
		SEG.RDB$INDEX_NAME EQ indexname
		SORTED BY SEG.RDB$FIELD_POSITION

		++n;
		if (count_only)
			continue;

		// Place a comma and a blank between each segment column name

		fb_utils::exact_name(SEG.RDB$FIELD_NAME);
		if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) {
			IUTILS_copy_SQL_id (SEG.RDB$FIELD_NAME, SQL_identifier, DBL_QUOTE);
		}
		else {
			strcpy (SQL_identifier, SEG.RDB$FIELD_NAME);
		}

		const size_t len = strlen(SQL_identifier);

		if (n == 1)
		{
			// We assume the buffer is at least size(metadata name), so no initial check.
			strcpy(segs, SQL_identifier);
			segs += len;
		}
		else
		{
			if (segs + len + 2 >= segs_end)
			{
				strncpy(segs, ", ...", segs_end - segs);
				*segs_end = '\0';
				count_only = true;
			}
			else
			{
				sprintf (segs, ", %s", SQL_identifier);
				segs += len + 2;
			}
		}

	END_FOR
	ON_ERROR
		ISQL_errmsg(fbStatus);
		ROLLBACK;
		return 0;
	END_ERROR;

	return n;
}


bool ISQL_get_base_column_null_flag(const TEXT* view_name,
									const SSHORT view_context,
									const TEXT* base_field)
{
/**************************************
 *
 *	I S Q L _ g e t _ b a s e _ c o l u m n _ n u l l _ f l a g
 *
 **************************************
 *
 *	Determine if a field on which view column is based
 *	is nullable. We are passed the view_name
 *	view_context and the base_field of the view column.
 *
 **************************************/
	BASED_ON RDB$RELATION_FIELDS.RDB$RELATION_NAME save_view_name,
		save_base_field;

	strcpy(save_view_name, view_name);
	strcpy(save_base_field, base_field);
	SSHORT save_view_context = view_context;


	if (!frontendTransaction())
		return false;

	/*
	Using view_name and view_context get the relation name from
	RDB$VIEW_RELATIONS which contains the base_field for this view column.
	Get row corresponding to this base field and relation from
	rdb$field_relations. This will contain info on field's nullability unless
	it is a view column itself, in which case repeat this procedure till
	we get to a "real" column.
	*/
	bool null_flag = true;
	bool done = false;
	bool error = false;
	while (!done && !error)
	{
		fb_utils::exact_name(save_view_name);
		fb_utils::exact_name(save_base_field);
		bool found = false;
		FOR FIRST 1
			VR IN RDB$VIEW_RELATIONS
			CROSS NEWRFR IN RDB$RELATION_FIELDS WITH
			VR.RDB$VIEW_NAME EQ save_view_name AND
			VR.RDB$VIEW_CONTEXT EQ save_view_context AND
			NEWRFR.RDB$RELATION_NAME = VR.RDB$RELATION_NAME AND
			NEWRFR.RDB$FIELD_NAME = save_base_field

			found = true;
			if (NEWRFR.RDB$BASE_FIELD.NULL)
			{
				if (!NEWRFR.RDB$NULL_FLAG.NULL && NEWRFR.RDB$NULL_FLAG == 1)
					null_flag = false;

				done = true;
			}
			else
			{
				strcpy (save_view_name, NEWRFR.RDB$RELATION_NAME);
				save_view_context = NEWRFR.RDB$VIEW_CONTEXT;
				strcpy (save_base_field, NEWRFR.RDB$BASE_FIELD);
			}
		END_FOR
		ON_ERROR
			error = true;
		END_ERROR;

		if (!found)
		{
			if (isqlGlob.major_ods >= ODS_VERSION12)
			{
				// Copy/paste from DYN_UTIL_find_field_source
				FOR FIRST 1
					VRL IN RDB$VIEW_RELATIONS CROSS
						PPR IN RDB$PROCEDURE_PARAMETERS
						WITH VRL.RDB$RELATION_NAME EQ PPR.RDB$PROCEDURE_NAME AND
						VRL.RDB$VIEW_NAME EQ save_view_name AND
						VRL.RDB$VIEW_CONTEXT EQ save_view_context AND
						VRL.RDB$CONTEXT_TYPE EQ VCT_PROCEDURE AND
						PPR.RDB$PACKAGE_NAME EQUIV VRL.RDB$PACKAGE_NAME AND
						PPR.RDB$PARAMETER_NAME EQ save_base_field AND
						PPR.RDB$PARAMETER_TYPE = 1 // output

					found = true;
					if (!PPR.RDB$NULL_FLAG.NULL && PPR.RDB$NULL_FLAG == 1)
						null_flag = false;

					done = true;
				END_FOR
				ON_ERROR
					error = true;
				END_ERROR;
			}
			else if (ENCODE_ODS(isqlGlob.major_ods, isqlGlob.minor_ods) >= ODS_11_2)
			{
				FOR FIRST 1
					VR IN RDB$VIEW_RELATIONS
					CROSS NEWPP IN RDB$PROCEDURE_PARAMETERS WITH
					VR.RDB$VIEW_NAME EQ save_view_name AND
					VR.RDB$VIEW_CONTEXT EQ save_view_context AND
					NEWPP.RDB$PROCEDURE_NAME = VR.RDB$RELATION_NAME AND
					NEWPP.RDB$PARAMETER_NAME = save_base_field AND
					NEWPP.RDB$PARAMETER_TYPE = 1 // output param

					found = true;
					if (!NEWPP.RDB$NULL_FLAG.NULL && NEWPP.RDB$NULL_FLAG == 1)
						null_flag = false;

					done = true;
				END_FOR
				ON_ERROR
					error = true;
				END_ERROR;
			}
		}

		if (!found)
			error = true;
	}

	// The error shouldn't be masked here. It should be propagated.
	return null_flag;
}

bool ISQL_get_null_flag(const TEXT* rel_name,
						TEXT* field_name)
{
/**************************************
 *
 *	I S Q L _ g e t _ n u l l _ f l a g
 *
 **************************************
 *
 *	Determine if a field has the null flag set.
 *	Look for either rdb$relation_fields or rdb$fields to be
 *	Set to 1 (NOT NULL), then this field cannot be null
 *	We are passed the relation name and the relation_field name
 *  	For domains, the relation name is null.
 **************************************/
	fb_utils::exact_name(field_name);

	bool null_flag = true;

	if (!frontendTransaction())
		return false;

	if (rel_name)
	{
		FOR FLD IN RDB$FIELDS CROSS
			RFR IN RDB$RELATION_FIELDS WITH
			RFR.RDB$FIELD_SOURCE EQ FLD.RDB$FIELD_NAME AND
			RFR.RDB$RELATION_NAME EQ rel_name AND
			RFR.RDB$FIELD_NAME EQ field_name

			if (!FLD.RDB$NULL_FLAG.NULL && FLD.RDB$NULL_FLAG == 1)
				null_flag = false;
			else
			{

				// If RDB$BASE_FIELD is not null then it is a view column

				if (RFR.RDB$BASE_FIELD.NULL)
				{

					// Simple column. Did user define it not null?

					if (!RFR.RDB$NULL_FLAG.NULL && RFR.RDB$NULL_FLAG == 1)
						null_flag = false;
				}
				else
				{
					null_flag = ISQL_get_base_column_null_flag (rel_name,
						RFR.RDB$VIEW_CONTEXT, RFR.RDB$BASE_FIELD);
				}
			}

		END_FOR
		ON_ERROR
			ISQL_errmsg(fbStatus);
			return null_flag;
		END_ERROR;
	}
	else
	{
		// Domains have only field entries to worry about
		FOR FLD IN RDB$FIELDS WITH
			FLD.RDB$FIELD_NAME EQ field_name

			if (FLD.RDB$NULL_FLAG == 1)
				null_flag = false;

		END_FOR
		ON_ERROR
			ISQL_errmsg(fbStatus);
			return null_flag;
		END_ERROR;
	}

	return null_flag;
}


void ISQL_disconnect_database(bool nQuietMode)
 {
/**************************************
 *
 *	I S Q L _ d i s c o n n e c t _ d a t a b a s e
 *
 **************************************
 *
 * Functional description
 *	Disconnect from the current database.  First commit work and then
 *	call detach to detach from the database and then zero
 *	out the DB handle.
 *
 * Change from miroslavp on 2005.05.06
 * Quiet global variable is preserved in local variable and restored at the end.
 **************************************/

	const bool OriginalQuiet = Quiet;
	// Ignore error msgs during disconnect
	Quiet = nQuietMode;

	// If we were in a database, commit before proceeding
	if (DB && (M__trans || D__trans))
		end_trans();

	// Commit transaction that was started on behalf of the request
	// Don't worry about the error if one occurs it will be network
	// related and caught later.
	if (DB && fbTrans)
	{
		fbTrans->rollback(fbStatus);
		if (succeeded())
			fbTrans = NULL;
	}

	// If there is  current user statement, free it
	if (global_Stmt)
	{
		global_Stmt->free(fbStatus);
		if (succeeded())
			global_Stmt = NULL;
	}

	// Detach from old database
	if (DB)
	{
		DB->detach(fbStatus);
		if (succeeded())
			DB = NULL;
	}

	// Restore original value of the flag
	Quiet = OriginalQuiet;

	// Zero database handle and transaction handles
	global_Stmt = NULL;
	DB = NULL;
	isqlGlob.global_Db_name[0] = '\0';
	D__trans = NULL;
	M__trans = NULL;
	fbTrans = NULL;

	// CVC: If we aren't connected to a db anymore, then the db's dialect is reset.
	// This should fix SF Bug #910430.
	isqlGlob.db_SQL_dialect = 0;
	// BRS this is also needed to fix #910430.
	global_dialect_spoken = 0;
	return;
}


#ifdef NOT_USED_OR_REPLACED
bool ISQL_is_domain(const TEXT* field_name)
{
/**************************************
 *
 *	I S Q L _ i s _ d o m a i n
 *
 **************************************
 *
 *	Determine if a field in rdb$fields is a domain,
 *
 **************************************/
	bool is_domain = false;

	if (!frontendTransaction())
		return false;

	FOR FLD IN RDB$FIELDS WITH
		FLD.RDB$FIELD_NAME EQ field_name

		if (!(implicit_domain(FLD.RDB$FIELD_NAME) && FLD.RDB$SYSTEM_FLAG != 1))
		{
			is_domain = true;
		}

	END_FOR
	ON_ERROR
		ISQL_errmsg(fbStatus);
		return is_domain;
	END_ERROR;

	return is_domain;
}
#endif


// Print the numeric type as accurately as possible, depending on the ODS.
// If it isn't numeric/decimal or is another non-numeric data type, print only the type.
bool ISQL_printNumericType(const char* fieldName, const int fieldType, const int fieldScale)
{
	// Look through types array
	int i = 0;
	while (Column_types[i].type && fieldType != Column_types[i].type)
		++i;

	fb_assert(Column_types[i].type != 0);
	if (Column_types[i].type == 0)
	{
		isqlGlob.printf("Unknown data type %d for field name %s%s", fieldType, fieldName, NEWLINE);
		return false;
	}

	bool precision_known = false;

	if (isqlGlob.major_ods >= ODS_VERSION10 &&
		(fieldType == SMALLINT || fieldType == INTEGER || fieldType == BIGINT))
	{
		// Handle Integral subtypes NUMERIC and DECIMAL
		// We are ODS >= 10 and could be any Dialect
		FOR FLD1 IN RDB$FIELDS WITH
			FLD1.RDB$FIELD_NAME EQ fieldName
			AND NOT FLD1.RDB$FIELD_PRECISION EQ 0

			// We are Dialect >=3 since FIELD_PRECISION is non-NULL
			if (FLD1.RDB$FIELD_SUB_TYPE > 0 && FLD1.RDB$FIELD_SUB_TYPE <= MAX_INTSUBTYPES)
			{
				isqlGlob.printf("%s(%d, %d)",
						 Integral_subtypes[FLD1.RDB$FIELD_SUB_TYPE],
						 FLD1.RDB$FIELD_PRECISION,
						 -FLD1.RDB$FIELD_SCALE);
				precision_known = true;
			}
		END_FOR
		ON_ERROR
			ISQL_errmsg (fbStatus);
			return false;
		END_ERROR;
	}

	if (!precision_known)
	{
		if (fieldScale < 0)
		{
			// Take a stab at numerics and decimals
			switch (fieldType)
			{
			case SMALLINT:
				isqlGlob.printf("NUMERIC(4, %d)", -fieldScale);
				break;
			case INTEGER:
				isqlGlob.printf("NUMERIC(9, %d)", -fieldScale);
				break;
			case BIGINT:
				isqlGlob.printf("NUMERIC(18, %d)", -fieldScale);
				break;
			case DOUBLE_PRECISION:
				isqlGlob.printf("NUMERIC(15, %d)", -fieldScale);
				break;
			default:
				isqlGlob.printf("%s", Column_types[i].type_name);
			}
		}
		else
		{
			// This is a non-numeric data type
			isqlGlob.printf("%s", Column_types[i].type_name);
		}
	}

	return true;
}


void ISQL_print_validation(FILE* fp,
						   ISC_QUAD* blobid,
						   bool isComputedField,
						   Firebird::ITransaction* trans)
{
/**************************************
 *
 *	I S Q L _ p r i n t _ v a l i d a t i o n
 *
 **************************************
 *
 * Functional description
 *	This does some minor syntax adjustmet for extracting
 *	validation blobs and computed fields.
 *	if it does not start with the word CHECK
 *	if this is a computed field blob, look for () or insert them.
 *	if flag == false, this is a validation clause,
 *	if flag == true, this is a computed field
 *
 **************************************/
	// Don't bother with null blobs

	if (blobid->gds_quad_high == 0 || !DB)
		return;

	Firebird::IBlob* blob = DB->openBlob(fbStatus, trans, blobid, 0, NULL);
	if (ISQL_errmsg(fbStatus))
		return;

	bool issql = false;
	bool firsttime = true;
	TEXT buffer[BUFFER_LENGTH512];
	Firebird::string fullText;

	do
	{
		unsigned int length;
		int cc = blob->getSegment(fbStatus, sizeof(buffer) - 1, buffer, &length);
		if (cc == Firebird::IStatus::RESULT_NO_DATA || cc == Firebird::IStatus::RESULT_ERROR)
			break;

		buffer[length] = 0;
		const TEXT* p = buffer;
		if (isComputedField)
		{
			// computed field SQL syntax correction

			while (fb_isspace(*p))
				p++;
			if (*p == '(')
				issql = true;
		}
		else
		{
			// Validation SQL syntax correction

			while (fb_isspace(*p))
				p++;
			if (!fb_utils::strnicmp(p, "CHECK", 5))
				issql = true;
		}
		if (firsttime)
		{
			firsttime = false;
			if (!issql) {
				IUTILS_printf2(fp, "%s ", (isComputedField ? "/* " : "("));
			}
		}

		IUTILS_printf(fp, buffer);
		fullText += buffer;
	} while (true);

	// CVC: If firsttime == true, then it didn't write the "/*" or the "(".
	if (!issql && !firsttime)
		IUTILS_printf2(fp, "%s", (isComputedField ? " */" : ")"));

	if (failed())
		ISQL_errmsg(fbStatus);

	blob->close(fbStatus);

	if (ISQL_statement_ends_in_comment(fullText.c_str()))
		fputc('\n', fp);
}


static processing_state add_row(TEXT* tabname)
{
/**************************************
 *
 *	a d d _ r o w
 *
 **************************************
 *
 * Functional description
 *	Allows interactive insert of row, prompting for every column
 *
 *	The technique is to describe a select query of select * from the table.
 *	Then generate an insert with ? in every position, using the same sqlda.
 *	It will allow edits of blobs, skip arrays and computed fields
 *
 *	Parameters:
 *	tabname -- Name of table to insert into
 *
 **************************************/
	if (!*tabname)
		return (ps_ERR);

	if (!Interactive)
		return (ps_ERR);

	if (Input_file || !DB)
		return ps_ERR;

	// There may have been a commit transaction before we got here
	if (!M_Transaction())
	{
		return FAIL;
	}

	// Start default transaction for prepare
	if (!D_Transaction())
	{
		return FAIL;
	}

	chop_at(tabname, QUOTEDLENGTH);
	if (tabname[0] != DBL_QUOTE)
		IUTILS_make_upper(tabname);

	// Query to obtain relation information
	Firebird::string str2;
	str2.printf("SELECT * FROM %s", tabname);

	if (global_Stmt)
	{
		global_Stmt->free(fbStatus);
		if (ISQL_errmsg(fbStatus))
			return (SKIP);
	}

	global_Stmt = DB->prepare(fbStatus, D__trans, 0, str2.c_str(), isqlGlob.SQL_dialect,
		Firebird::IStatement::PREPARE_PREFETCH_METADATA);
	if (ISQL_errmsg(fbStatus))
		return (SKIP);

	Firebird::RefPtr<Firebird::IMessageMetadata> msg(Firebird::REF_NO_INCR, global_Stmt->getOutputMetadata(fbStatus));
	if (ISQL_errmsg(fbStatus))
		return (SKIP);

	const unsigned n_cols = msg->getCount(fbStatus);
	if (ISQL_errmsg(fbStatus))
		return (SKIP);

	global_Stmt->free(fbStatus);
	if (ISQL_errmsg(fbStatus))
		return (SKIP);
	global_Stmt = NULL;

	// Array for storing select/insert column mapping, colcheck sets it up
	Firebird::Array<unsigned> coln;
	unsigned* colnumber = coln.getBuffer(n_cols);
	col_check(tabname, colnumber);

	// Create the new INSERT statement from the sqlda info

	Firebird::string insertstring;

	// There is a question mark for each column that's known to be updatable.

	insertstring.printf("INSERT INTO %s (", tabname);

	unsigned i_cols = 0;
	{ // scope
		char fldfixed[QUOTEDLENGTH];
		int i = 0;
		for (unsigned i = 0; i < n_cols; ++i)
		{
			// Skip columns that are computed
			if (colnumber[i] != ~0u)
			{
				const char* fldname = msg->getField(fbStatus, i);
				if (ISQL_errmsg(fbStatus))
					return (SKIP);
				const bool delimited_yes = fldname[0] == DBL_QUOTE;
				if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes)
				{
					IUTILS_copy_SQL_id(fldname, fldfixed, DBL_QUOTE);
				}
				else
				{
					strcpy(fldfixed, fldname);
					IUTILS_make_upper(fldfixed);
				}

				// Set i_cols to the number of insert columns
				if (i_cols++)
					insertstring += ',';
				insertstring += fldfixed;
			}
		}
	} // scope

	insertstring += ") VALUES (";

	for (unsigned i = 0; i < i_cols; i++)
	{
		if (i)
			insertstring += ',';
		insertstring += '?';
	}

	insertstring += ')';

	// Build metadata for insert message
	if (i_cols != n_cols)
	{
		Firebird::RefPtr<Firebird::IMetadataBuilder> bldr(Firebird::REF_NO_INCR, msg->getBuilder(fbStatus));
		if (ISQL_errmsg(fbStatus))
			return (SKIP);

		unsigned i = n_cols;
		while (i-- > 0)
		{
			if (colnumber[i] == ~0u)
			{
				bldr->remove(fbStatus, i);
				if (ISQL_errmsg(fbStatus))
					return (SKIP);
			}
		}
		msg.assignRefNoIncr(bldr->getMetadata(fbStatus));
		if (ISQL_errmsg(fbStatus))
			return (SKIP);
	}

	// Allocate INSERT buffer
	unsigned bSize = msg->getMessageLength(fbStatus);
	if (ISQL_errmsg(fbStatus))
		return (SKIP);
	UCHAR* msgBuf = global_Buffer->getBuffer(bSize);

	// For each column, get the type, and length then prompt for a value
	// and scanf the resulting string into a buffer of the right type.

	SCHAR infobuf[BUFFER_LENGTH180];
	IUTILS_msg_get(ADD_PROMPT, sizeof(infobuf), infobuf);
	STDERROUT(infobuf);

	TEXT txt[MSG_LENGTH];
	bool done = false;
	while (!done)
	{
		for (unsigned i = 0; i < i_cols && !done; ++i)
		{
			short* nullp = (short*)&msgBuf[msg->getNullOffset(fbStatus, i)];
			if (ISQL_errmsg(fbStatus))
				return (SKIP);
			UCHAR* datap = &msgBuf[msg->getOffset(fbStatus, i)];
			if (ISQL_errmsg(fbStatus))
				return (SKIP);

			const char* name = msg->getField(fbStatus, i);
			if (ISQL_errmsg(fbStatus))
				return (SKIP);
			unsigned type = msg->getType(fbStatus, i);
			if (ISQL_errmsg(fbStatus))
				return (SKIP);
			const bool nullable = msg->isNullable(fbStatus, i);
			if (ISQL_errmsg(fbStatus))
				return (SKIP);
			unsigned varLength;
			int scale;

			// Prompt with the name and read the line
			switch (type)
			{
			case SQL_BLOB:
				IUTILS_msg_get(BLOB_PROMPT, txt, SafeArg() << name);
				// Blob: %s, type 'edit' or filename to load>
				break;
			case SQL_TYPE_DATE:
				IUTILS_msg_get(DATE_PROMPT, txt, SafeArg() << name);
				// Enter %s as Y/M/D>
				break;
			case SQL_TYPE_TIME:
				IUTILS_msg_get(TIME_PROMPT, txt, SafeArg() << name);
				// Enter %s as H:M:S>
				break;
			case SQL_TIMESTAMP:
				IUTILS_msg_get(TIMESTAMP_PROMPT, txt, SafeArg() << name);
				// Enter %s as Y/MON/D H:MIN:S[.MSEC]>
				break;
			default:
				IUTILS_msg_get(NAME_PROMPT, txt, SafeArg() << name);
				// Enter %s>
				break;
			}

			// On blank line or EOF, break the two loops without doing an insert.

			readNextInputLine(txt);
			getColumn = -1; // We are bypassing getNextInputChar().
			if (lastInputLine == NULL || strlen(lastInputLine) == 0)
			{
				done = true;
				break;
			}

			unsigned length = static_cast<unsigned>(strlen(lastInputLine));
			if (length > MAX_SQL_LENGTH)
			{
				IUTILS_msg_get(BUFFER_OVERFLOW, txt);
				STDERROUT(txt);
				done = true;
				break;
			}
			STDERROUT("");

			// Convert first 4 chars to upper case for comparison
			SCHAR cmd[5];
			strncpy(cmd, lastInputLine, 4);
			cmd[4] = '\0';
			IUTILS_make_upper(cmd);

			// If the user writes NULL, put a null in the column

			if (!strcmp(cmd, "NULL"))
				*nullp = -1;
			else
			{
				*nullp = 0;
				SLONG res;

				// Data types
				SSHORT* smallint;
				SLONG* integer;
				SINT64* pi64;
				SINT64 n;
				float* fvalue;
				double* dvalue;
				UCHAR* boolean;
				ISC_QUAD* blobid;
				vary* avary;
				char* achar;
				tm times;
				// Initialize the time structure
				memset(&times, 0, sizeof(times));
				char msec_str[5] = "";
				int msec = 0;

				switch (type)
				{
				case SQL_BLOB:

					blobid = (ISC_QUAD*) datap;

					if (!strcmp(cmd, "EDIT")) // If edit, we edit a temp file.
					{
						const Firebird::PathName ftmp = TempFile::create(SCRATCH);
						if (ftmp.empty())
							res = 0;
						else
						{
							gds__edit(ftmp.c_str(), 0);
							Firebird::UtilInterfacePtr()->loadBlob(fbStatus, blobid, DB, M__trans,
								ftmp.c_str(), FB_TRUE);
							unlink(ftmp.c_str());
							res = succeeded();
						}
					}
					else	// Otherwise just load the named file
					{
						// We can't be sure if it's a TEXT or BINARY file
						// being loaded.  As we aren't sure, we'll
						// do binary operation - this is NEW data in
						// the database, not updating existing info
						Firebird::UtilInterfacePtr()->loadBlob(fbStatus, blobid, DB, M__trans,
							lastInputLine, FB_FALSE);
						res = succeeded();
					}

					if (!res)
					{
						STDERROUT("Unable to load file");
						done = true;
					}
					break;

				case SQL_FLOAT:
					fvalue = (float*) datap;
					if (sscanf(lastInputLine, "%g", fvalue) != 1)
					{
						STDERROUT("Input parsing problem");
						done = true;
					}
					break;

				case SQL_DOUBLE:
					dvalue = (double*) datap;
					if (sscanf(lastInputLine, "%lg", dvalue) != 1)
					{
						STDERROUT("Input parsing problem");
						done = true;
					}
					break;

				case SQL_TYPE_DATE:
					if (3 != sscanf(lastInputLine, "%d/%d/%d", &times.tm_year,
									&times.tm_mon, &times.tm_mday) ||
						!check_date(times))
					{
						IUTILS_msg_get(DATE_ERR, txt, SafeArg() << lastInputLine);
						STDERROUT(txt);	// Bad date %s\n
						done = true;
					}
					else
					{
						--times.tm_mon;
						times.tm_year -= 1900; // tm_year is 1900-based.
						ISC_DATE* date = (ISC_DATE*) datap;
						isc_encode_sql_date(&times, date);
					}
					break;

				case SQL_TYPE_TIME:
					if (3 != sscanf(lastInputLine, "%d:%d:%d", &times.tm_hour,
									&times.tm_min, &times.tm_sec) ||
						!check_time(times))
					{
						IUTILS_msg_get(TIME_ERR, txt, SafeArg() << lastInputLine);
						STDERROUT(txt);	// Bad time %s\n
						done = true;
					}
					else
					{
						ISC_TIME* vtime = (ISC_TIME*) datap;
						isc_encode_sql_time(&times, vtime);
					}
					break;

				case SQL_TIMESTAMP:
					if (6 <= sscanf(lastInputLine, "%d/%d/%d %d:%d:%d.%4s",
									&times.tm_year, &times.tm_mon, &times.tm_mday,
									&times.tm_hour, &times.tm_min, &times.tm_sec,
									msec_str))
					{
						unsigned int count = 0;
						for (; count < 4; ++count)
						{
							if (fb_isdigit(msec_str[count]))
								msec = msec * 10 + msec_str[count] - '0';
							else
								break;
						}
						if (count != strlen(msec_str))
							done = true;
						else
						{
							while (count++ < 4)
								msec *= 10;
							done = !check_timestamp(times, msec);
						}
					}
					else
						done = true;
					if (done)
					{
						IUTILS_msg_get(TIMESTAMP_ERR, txt, SafeArg() << lastInputLine);
						STDERROUT(txt);	// Bad timestamp %s\n
					}
					else
					{
						--times.tm_mon;
						times.tm_year -= 1900;
						ISC_TIMESTAMP* datetime = (ISC_TIMESTAMP*) datap;
						isc_encode_timestamp(&times, datetime);
						datetime->timestamp_time += msec;
					}
					break;

				case SQL_TEXT:
				case SQL_VARYING:
					varLength = msg->getLength(fbStatus, i);
					if (ISQL_errmsg(fbStatus))
						return (SKIP);
					if (length > varLength)
					{
						STDERROUT("String too long");		// msg!
						length = varLength;
					}

					if (type == SQL_VARYING)
					{
						avary = (vary*) datap;
						avary->vary_length = length;
						memcpy(avary->vary_string, lastInputLine, length);
					}
					else
					{
						achar = (char*) datap;
						memcpy(achar, lastInputLine, length);
						memset(&achar[length], ' ', varLength - length);
					}
					break;

				case SQL_SHORT:
				case SQL_LONG:
				case SQL_INT64:
					smallint = (SSHORT*) datap;
					integer = (SLONG*) datap;
					pi64 = (SINT64*) datap;

					scale = msg->getScale(fbStatus, i);
					if (ISQL_errmsg(fbStatus))
						return (SKIP);

					if (scale < 0)
					{
						SSHORT lscale = 0;
						if (!get_numeric((UCHAR*) lastInputLine, length, &lscale, &n))
						{
							STDERROUT("Input parsing problem");
							done = true;
						}
						else
						{
							int dscale = scale - lscale;
							if (dscale > 0)
							{
								TEXT err_buf[256];
								sprintf(err_buf,
										"input error: input scale %d exceeds the field's scale %d",
										-lscale, -scale);
								STDERROUT(err_buf);
								done = true;
							}
							while (dscale++ < 0)
								n *= 10;
						}
					}
					// sscanf assumes a 64-bit integer target
					else if (sscanf(lastInputLine, "%" SQUADFORMAT, &n) != 1)
					{
						STDERROUT("Input parsing problem");
						done = true;
					}
					if (done)
						break; // Nothing else, we found an error.

					switch (type)
					{
					case SQL_INT64:
						*pi64 = n;
						break;
					case SQL_SHORT:
						*smallint = n;
						if (SINT64(*smallint) != n)
						{
							STDERROUT("Value too big");
							done = true;
						}
						break;
					case SQL_LONG:
						*integer = n;
						if (SINT64(*integer) != n)
						{
							STDERROUT("Value too big");
							done = true;
						}
					}
					break;

				case SQL_BOOLEAN:
					boolean = (FB_BOOLEAN*) datap;

					if (fb_utils::stricmp(lastInputLine, "TRUE") == 0)
						*boolean = 1;
					else if (fb_utils::stricmp(lastInputLine, "FALSE") == 0)
						*boolean = 0;
					else
					{
						STDERROUT("Invalid boolean value");
						done = true;
					}
					break;

				default:
					done = true;
					STDERROUT("Unexpected SQLTYPE in add_row()");
					break;
				}
			}
		}
		if (!done)
		{
			// having completed all columns, try the insert statement with the msg

			DB->execute(fbStatus, M__trans, insertstring.length(), insertstring.c_str(),
				isqlGlob.SQL_dialect, msg, msgBuf, NULL, NULL);
			if (ISQL_errmsg(fbStatus))
			{
				break;
			}
		}
	}

	return (SKIP);
}


static processing_state blobedit(const TEXT* action, const TEXT* const* cmd)
{
/**************************************
 *
 *	b l o b e d i t
 *
 **************************************
 *
 * Functional description
 *	Edit the text blob indicated by blobid
 *
 *	Parameters:  cmd -- Array of words interpreted as file name
 *
 **************************************/
	if (!ISQL_dbcheck())
		return FAIL;

	if (*cmd[1] == 0)
		return ps_ERR;

	const TEXT* p = cmd[1];

	// Find the high and low values of the blob id
	ISC_QUAD blobid;
	sscanf(p, "%" xLONGFORMAT":%" xLONGFORMAT, &blobid.gds_quad_high, &blobid.gds_quad_low);

	// If it isn't an explicit blobedit, then do a dump. Since this is a
	// user operation, put it on the M__trans handle.

	processing_state rc = SKIP;
	if (!strcmp(action, "BLOBVIEW"))
	{
		Firebird::UtilInterfacePtr utl;
		PathName tmpf = TempFile::create(fbStatus, "blob");
		if (ISQL_errmsg(fbStatus))
			return ps_ERR;
		const char* filename = tmpf.c_str();

		utl->dumpBlob(fbStatus, &blobid, DB, M__trans, filename, FB_TRUE);
		if (ISQL_errmsg(fbStatus))
			rc = ps_ERR;
		else
			gds__edit(filename, 0);

		unlink(filename);
	}
	else if ((!strcmp(action, "BLOBDUMP")) && (*cmd[2]))
	{
		// If this is a blobdump, make sure there is a file name
		// We can't be sure if the BLOB is TEXT or BINARY data,
		// as we're not sure, we'll dump it in BINARY mode.
		TEXT path[MAXPATHLEN];
		strip_quotes(cmd[2], path);
		Firebird::UtilInterfacePtr()->dumpBlob(fbStatus, &blobid, DB, M__trans, path, FB_FALSE);
	}
	else
		rc = ps_ERR;

	if (rc == SKIP && ISQL_errmsg(fbStatus))
	{
		rc = ps_ERR;
	}

	return rc;
}


// *******************************
// b u l k _ i n s e r t _ h a c k
// *******************************
// Primitive processing for prepared insertions. Invocation is
// SET BULK_INSERT <insert_statement>
// (val1, ..., valN)
// (val1, ..., valN)
// Finish with an empty line or anything different than an opening parenthesis.
// For example, STOP may be explicit, but any word without '(' will do the trick.
// Tuples must go in a single line. Only quoted strings can span more than one line.
// Added a very visible +++ to put other parameters of the same row in another line.
// Use +++ after a comma and continue in the next line. No comments in the middle.
// Only single quote accepted. Strings without special characters can go unquoted.
// Use the default question mark (?) to designate parameters.
// Single line comments are recognized only in the first column on a separate line
// and they can only appear before or after full tuples (not between multi-line tuples).
// The command COMMIT or COMMIT WORK can appear only in the first column in a
// single line and it will be recognized. We do not check if there's more text
// in the same line, thus it can be the terminator or random garbage.
// Two commas are invalid as empty/NULL value, use NULL or the appropriate "empty" value
// for the data type instead (zero for numbers, two single quotes for string, etc.)
// The code needs review, cleanup and moving some messages to the msg db.
// Since the code is forgiving, it will check for double ')' in the row but you can
// write (val1, ..., valN); with the terminator at the end and garbage following it.
// Indeed, the terminator can be replaced by unfamiliar characters to the parser
// like #, $, %, etc., and anything can follow them, including random text.
// It's invalid to put +++ (signaling row continuation) after a tuple is complete.
// If you came here looking for robust parsing code, you're at the wrong place.
static processing_state bulk_insert_hack(const char* command)
{
	// Skip "SET BULK_INSERT" part.
	for (int j = 0; j < 2; ++j)
	{
		while (*command && *command != 0x20)
			++command;
		while (*command && *command == 0x20)
			++command;
	}
	if (!*command)
		return ps_ERR;

	processing_state ret = SKIP;

	// If somebody did a commit or rollback, we are out of a transaction

	if (!M_Transaction())
		return ps_ERR;

	// No need to start a default transaction unless there is no current one

	if (setValues.Autocommit)
	{
		if (!D_Transaction())
			return ps_ERR;
	}

	// If statistics are requested, then reserve them here

	SINT64 perf_before[ISQL_COUNTERS];
	if (setValues.Stats)
	{
		Firebird::UtilInterfacePtr()->getPerfCounters(fbStatus,
			DB, ISQL_COUNTERS_SET, perf_before);
		if (ISQL_errmsg(fbStatus))
		{
			return ps_ERR;
		}
	}

	// Prepare the dynamic query stored in string.
	// But put this on the DDL transaction to get maximum visibility of
	// metadata.

	if (global_Stmt)
	{
		global_Stmt->free(fbStatus);
		if (ISQL_errmsg(fbStatus))
			return (SKIP);
	}

	unsigned prepFlags = Firebird::IStatement::PREPARE_PREFETCH_METADATA |
		(setValues.Plan ? Firebird::IStatement::PREPARE_PREFETCH_DETAILED_PLAN : 0);

	global_Stmt = DB->prepare(fbStatus, setValues.Autocommit ? D__trans : M__trans,  0, command,
		isqlGlob.SQL_dialect, prepFlags);

	if (failed())
	{
		if (isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION && Input_file)
		{
			isqlGlob.printf("%s%s%s%s%s%s",
							NEWLINE,
							"**** Error preparing statement:",
							NEWLINE,
							NEWLINE,
							command,
							NEWLINE);
		}
		ISQL_errmsg(fbStatus);
		return ps_ERR;
	}

	// check for warnings
	ISQL_warning(fbStatus);

	// Find out what kind of statement this is
	const int statement_type = process_request_type();
	if (!statement_type)
		return ps_ERR;

	/*** ASF: This is util to test others commands too.
	if (statement_type != isc_info_sql_stmt_insert)
	{
		isqlGlob.printf("Only INSERT commands are accepted in bulk mode.%s", NEWLINE);
		return ps_ERR;
	}
	***/

	Firebird::RefPtr<Firebird::IMessageMetadata> message(global_Stmt->getInputMetadata(fbStatus));
	if (ISQL_errmsg(fbStatus))
	{
		return ps_ERR;
	}

	if (setValues.Sqlda_display)
	{
		print_message(message, "IN");
	}

	const unsigned n_cols = message->getCount(fbStatus);
	if (ISQL_errmsg(fbStatus))
	{
		return ps_ERR;
	}
	if (!n_cols) // No input parameters, doesn't make sense for bulk insertions.
	{
		isqlGlob.printf("There must be at least one parameter in the statement.%s", NEWLINE);
		return ps_ERR;
	}

	// If PLAN is set, print out the plan now.

	if (setValues.Plan)
	{
		process_plan();
		if (setValues.Planonly)
			return ret;	// Do not execute.
	}

	unsigned msgSize = message->getMessageLength(fbStatus);
	if (ISQL_errmsg(fbStatus))
		return ps_ERR;
	UCHAR* msgBuf = global_Buffer->getBuffer(msgSize);

	char bulk_prompt[BUFFER_LENGTH180] = "";
	IUTILS_msg_get(BULK_PROMPT, sizeof(bulk_prompt), bulk_prompt);

	// Lookup the continuation prompt once
	TEXT con_prompt[MSG_LENGTH];
	IUTILS_msg_get(CON_PROMPT, con_prompt);

	TEXT msg[MSG_LENGTH];
	bool commit_failedM = false;
	bool commit_failedD = false;
	bool done = false; // This is mostly "done with errors".
	while (!done)
	{
		if (bulk_insert_retriever(bulk_prompt)) // We finished normally, found EOF.
			break;

		// We only support single line comments and they should be at the first column,
		// no spaces before, etc. Go to read the next line.
		if (lastInputLine[0] == '-' && lastInputLine[1] == '-')
			continue;

		const char* insert = lastInputLine;
		while (*insert && *insert != '(')
			++insert;

		if (!*insert || *insert != '(')
		{
			// Again, we are strict, we need COMMIT or COMMIT WORK exactly at the beginning
			// and it will be the only command in the line (we don't care about the rest).
			if (fb_utils::strnicmp(lastInputLine, "COMMIT", 6) == 0 ||
				fb_utils::strnicmp(lastInputLine, "COMMIT WORK", 11) == 0)
			{
				done = true;

				if (!commit_trans(&M__trans))
				{
					commit_failedM = true;
					break; // We failed to commit, quit the bulk insertion.
				}

				if (!M_Transaction())
				{
					commit_failedM = false;
					break; // We failed to start transaction, quit the bulk insertion.
				}

				// CVC: Commit may fail with AUTO-DDL off and DDL changes rejected by DFW.
				if (D__trans)
				{
					if (!commit_trans(&D__trans))
					{
						commit_failedD = true;
						break; // We failed to commit, quit the bulk insertion.
					}

					if (!D_Transaction())
					{
						commit_failedD = false;
						break; // We failed to commit, quit the bulk insertion.
					}
				}

				done = false;
				continue; // Go to read another line.
			}
			break; // For example, STOP or blank line not inside quoted string.
		}

		++insert;

		while (*insert == 0x20 || *insert == '\r' || *insert == '\t')
			++insert;

		if (!*insert) // Did we finish gracefully? The last line may be spaces or tab.
			break;

		const char* lastPos = insert;
		Extender extender; // Used only for multi-line tuples.

		for (unsigned i = 0, textFieldIter = 0; i < n_cols && !done; ++i)
		{
			unsigned offset = message->getNullOffset(fbStatus, i);
			if (ISQL_errmsg(fbStatus))
				return ps_ERR;
			short* nullp = (short*) &msgBuf[offset];

			offset = message->getOffset(fbStatus, i);
			if (ISQL_errmsg(fbStatus))
				return ps_ERR;
			void* datap = &msgBuf[offset];

			unsigned type = message->getType(fbStatus, i);
			if (ISQL_errmsg(fbStatus))
				return (SKIP);
			const bool nullable = message->isNullable(fbStatus, i);
			if (ISQL_errmsg(fbStatus))
				return (SKIP);

			const char* finder = 0;
			int subtract = 0;
			const bool quote = *insert == SINGLE_QUOTE;
			if (quote)
			{
				subtract = 2; // Ignore the quotes at the beginning and end.
				bool finished = false; // Did the string close?
				++insert;
				while (*insert)
				{
					if (*insert == SINGLE_QUOTE)
					{
						++insert;
						if (*insert == SINGLE_QUOTE)
							++subtract;
						else
						{
							finished = true;
							break;
						}
					}
					++insert;
				}
				if (!finished)
				{
					extender.alloc(MAX_USHORT);
					extender.append(lastPos, insert - lastPos);
					while (!finished && !bulk_insert_retriever(con_prompt))
					{
						finder = lastInputLine;
						while (*finder)
						{
							if (*finder == SINGLE_QUOTE)
							{
								++finder;
								if (*finder == SINGLE_QUOTE)
									++subtract;
								else
								{
									finished = true;
									break;
								}
							}
							++finder;
						}
						size_t how_many = finder - lastInputLine;
						if (extender.append(lastInputLine, how_many) <= how_many)
						{
							done = true;
							ret = ps_ERR; // Do not delete, see if() below.
							STDERROUT("Failed to concatenate string");
							break;
						}
					}
					if (ret != ps_ERR && !finished)
					{
						done = true;
						STDERROUT("Failed to find end of quoted string");
					}
					if (done)
						break;

					lastPos = extender.getBuffer();
					insert = lastPos + extender.getUsed();
				}

				if (subtract > 2)
				{
					// Get rid of pairs of single quotes; they are a syntax artifact.
					const char* view = lastPos + 1;
					// We are working over lastInputLine or Extender's buffer, so "unconst" is safe.
					char* mover = const_cast<char*>(lastPos + 1);
					for (int counter = subtract; view < insert; ++view, ++mover)
					{
						*mover = *view;
						if (*view == SINGLE_QUOTE && view[1] == SINGLE_QUOTE && counter > 2)
						{
							++view;
							--counter;
						}
					}
				}
			}
			else
			{
				//if ((type == SQL_TEXT || type == SQL_VARYING || type == SQL_BLOB)
				//	&& fb_utils::strnicmp(insert, "NULL", 4))
				//{
				//	STDERROUT("Looking for unquoted string in:");
				//	STDERROUT(insert);
				//}

				for (bool go = true; go && *insert; ++insert)
				{
					switch (*insert)
					{
					case 0x20:
					//case '\n':
					case '\r':
					case '\t':
					case ',':
					case ')':
						go = false;
						--insert;
						break;
					}
				}
			}

			SCHAR cmd[5] = "";
			unsigned length = insert - lastPos - subtract;
			if (!length && !quote)
			{
				// Go ahead with an aux var and see if we hit end of string.
				const char* ipeek = insert;
				//char s[3] = {ipeek[-1], ipeek[0] ? ipeek[0] : 'Z', 0};
				//STDERROUT(s);
				while (*ipeek == 0x20 || *ipeek == '\r' || *ipeek == '\t')
					++ipeek;

				//s[0] = ipeek[-1];
				//s[1] = ipeek[0] ? ipeek[0] : 'Z';
				//STDERROUT(s);
				switch (*ipeek)
				{
				case ',':
					STDERROUT("Fields with zero length only allowed in quoted strings");
					break;
				case ')':
					STDERROUT("Found ')' before reading all fields in a row");
					break;
				default:
					STDERROUT("Unterminated row, use +++ to put more parameters in another line");
				}

				done = true;
				continue;
			}

			if (quote)
				++lastPos;
			else
			{
				// Convert first 4 chars to upper case for comparison.
				strncpy(cmd, lastPos, 4);
				cmd[4] = '\0';
				IUTILS_make_upper(cmd);
			}

			// If the user writes NULL, put a null in the column.

			if (!strcmp(cmd, "NULL"))
				*nullp = -1;
			else
			{
				*nullp = 0;
				// Data types
				SSHORT* smallint;
				SLONG* integer;
				SINT64* pi64;
				SINT64 n;
				float* fvalue;
				double* dvalue;
				UCHAR* boolean;
				vary* avary;
				char* achar;
				tm times;
				// Initialize the time structure.
				memset(&times, 0, sizeof(times));
				char msec_str[5] = "";
				int msec = 0;
				ISC_QUAD* blobid;
				unsigned varLength;
				int scale;

				switch (type)
				{
				case SQL_BLOB:
					blobid = (ISC_QUAD*) datap;

					{ // scope
						Firebird::IBlob* bs = DB->createBlob(fbStatus, M__trans, blobid, 0, NULL);
						if (failed())
						{
							STDERROUT("Unable to create blob");
							ISQL_errmsg(fbStatus);
							done = true;
							break; // Quit the switch()
						}

						bs->putSegment(fbStatus, length, lastPos);
						if (failed())
						{
							STDERROUT("Unable to write to blob");
							ISQL_errmsg(fbStatus);
							done = true;
						}
						bs->close(fbStatus);
						if (failed())
						{
							STDERROUT("Unable to close blob");
							ISQL_errmsg(fbStatus);
							done = true;
						}
					} // scope

					break;

				case SQL_FLOAT:
					fvalue = (float*) datap;
					if (sscanf(lastPos, "%g", fvalue) != 1)
					{
						STDERROUT("Input parsing problem in 'float' value");
						done = true;
					}
					break;

				case SQL_DOUBLE:
					dvalue = (double*) datap;
					if (sscanf(lastPos, "%lg", dvalue) != 1)
					{
						STDERROUT("Input parsing problem in 'double' value");
						done = true;
					}
					break;

				case SQL_TYPE_DATE:
					if (3 != sscanf(lastPos, "%d-%d-%d", &times.tm_year,
									&times.tm_mon, &times.tm_mday) ||
						!check_date(times))
					{
						IUTILS_msg_get(DATE_ERR, msg, SafeArg() << lastPos);
						STDERROUT(msg);	// Bad date %s\n
						done = true;
					}
					else
					{
						--times.tm_mon;
						times.tm_year -= 1900; // tm_year is 1900-based.
						ISC_DATE* date = (ISC_DATE*) datap;
						isc_encode_sql_date(&times, date);
					}
					break;

				case SQL_TYPE_TIME:
					if (3 != sscanf(lastPos, "%d:%d:%d", &times.tm_hour,
									&times.tm_min, &times.tm_sec) ||
						!check_time(times))
					{
						IUTILS_msg_get(TIME_ERR, msg, SafeArg() << lastPos);
						STDERROUT(msg);	// Bad time %s\n
						done = true;
					}
					else
					{
						ISC_TIME* vtime = (ISC_TIME*) datap;
						isc_encode_sql_time(&times, vtime);
					}
					break;

				case SQL_TIMESTAMP:
					if (6 <= sscanf(lastPos, "%d-%d-%d %d:%d:%d.%4s",
									&times.tm_year, &times.tm_mon, &times.tm_mday,
									&times.tm_hour, &times.tm_min, &times.tm_sec,
									msec_str))
					{
						unsigned int count = 0;
						for (; count < 4; ++count)
						{
							if (fb_isdigit(msec_str[count]))
								msec = msec * 10 + msec_str[count] - '0';
							else
								break;
						}
						if (count != strlen(msec_str))
							done = true;
						else
						{
							while (count++ < 4)
								msec *= 10;
							done = !check_timestamp(times, msec);
						}
					}
					else
						done = true;

					if (done)
					{
						IUTILS_msg_get(TIMESTAMP_ERR, msg, SafeArg() << lastPos);
						STDERROUT(msg);	// Bad timestamp %s\n
					}
					else
					{
						--times.tm_mon;
						times.tm_year -= 1900;
						ISC_TIMESTAMP* datetime = (ISC_TIMESTAMP*) datap;
						isc_encode_timestamp(&times, datetime);
						datetime->timestamp_time += msec;
					}
					break;

				case SQL_TEXT:
				case SQL_VARYING:
					varLength = message->getLength(fbStatus, i);
					if (ISQL_errmsg(fbStatus))
						return (SKIP);
					if (length > varLength)
					{
						STDERROUT("String too long");		// msg!
						length = varLength;
					}

					if (type == SQL_VARYING)
					{
						avary = (vary*) datap;
						avary->vary_length = length;
						memcpy(avary->vary_string, lastPos, length);
					}
					else
					{
						achar = (char*) datap;
						memcpy(achar, lastPos, length);
						memset(&achar[length], ' ', varLength - length);
					}
					break;

				case SQL_SHORT:
				case SQL_LONG:
				case SQL_INT64:
					smallint = (SSHORT*) datap;
					integer = (SLONG*) datap;
					pi64 = (SINT64*) datap;

					scale = message->getScale(fbStatus, i);
					if (ISQL_errmsg(fbStatus))
						return (SKIP);

					if (scale < 0)
					{
						SSHORT lscale = 0;
						if (!get_numeric((UCHAR*) lastPos, length, &lscale, &n))
						{
							STDERROUT("Input parsing problem in 'numeric' or 'decimal' value");
							done = true;
						}
						else
						{
							int dscale = scale - lscale;
							if (dscale > 0)
							{
								TEXT err_buf[256];
								sprintf(err_buf,
										"Input error: input scale %d exceeds the field's scale %d",
										-lscale, -scale);
								STDERROUT(err_buf);
								done = true;
							}
							while (dscale++ < 0)
								n *= 10;
						}
					}
					// sscanf assumes a 64-bit integer target
					else if (sscanf(lastPos, "%" SQUADFORMAT, &n) != 1)
					{
						STDERROUT("Input parsing problem in 'integer' value");
						done = true;
					}
					if (done)
						continue; // Nothing else, we found an error.

					switch (type)
					{
					case SQL_INT64:
						*pi64 = n;
						break;
					case SQL_SHORT:
						*smallint = n;
						if (SINT64(*smallint) != n)
						{
							STDERROUT("Integer value too big");
							done = true;
						}
						break;
					case SQL_LONG:
						*integer = n;
						if (SINT64(*integer) != n)
						{
							STDERROUT("Integer value too big");
							done = true;
						}
					}
					break;

				case SQL_BOOLEAN:
					boolean = (FB_BOOLEAN*) datap;
					if (fb_utils::stricmp(lastInputLine, "TRUE") == 0)
						*boolean = 1;
					else if (fb_utils::stricmp(lastInputLine, "FALSE") == 0)
						*boolean = 0;
					else
					{
						STDERROUT("Invalid boolean value");
						done = true;
					}
					break;

				default:
					done = true;
					STDERROUT("Unexpected SQLTYPE in bulk_insert_hack()");
					break;
				}
			}

			if (done)
				break;

			// Restore "insert" pointer if we needed multi-line hack.
			if (finder)
				insert = finder;

			//while (*insert && *insert != ',' && *insert != ')')
			//	++insert;

			int comma_count = 0;
			int parenthesis_count = 0;
			for (bool go = true; go && *insert; ++insert)
			{
				switch (*insert)
				{
				case 0x20:
				//case '\n':
				case '\r':
				case '\t':
					break;
				case ',':
					if (++comma_count > 1)
					{
						go = false;
						--insert;
					}
					else if (i + 1 == n_cols) // We read all the row, no comma allowed!
					{
						go = false;
						done = true;
						STDERROUT("All fields were read but a comma was found");
					}
					break;
				case ')':
					if (++parenthesis_count > 1)
					{
						go = false;
						done = true;
						STDERROUT("Found more than one ')' in a row");
					}
					else if (i + 1 < n_cols) // We didn't read all fields but found closing parenthesis!
					{
						go = false;
						done = true;
						STDERROUT("Found ')' before reading all fields in a row");
					}
					break;
				default:
					go = false;
					--insert;
					break;
				}
			}

			// Allow line continuation after comma.
			if (!done && strncmp(insert, "+++", 3) == 0)
			{
				if (i + 1 == n_cols) // We read all the row, no continuation allowed!
				{
					done = true;
					STDERROUT("All fields were read but the continuation mark +++ was found");
				}
				else
				{
					if (bulk_insert_retriever(con_prompt))
					{
						done = true;
						STDERROUT("The continuation mark +++ was found but EOF was reached");
					}
					insert = lastInputLine;
				}
			}

			lastPos = insert;

		} // for (int i = 0;

		if (!done)
		{
			// Having completed all columns, try the insert statement with the message.
			// This is a non-select DML statement or trans.

			global_Stmt->execute(fbStatus, M__trans, message, msgBuf, NULL, NULL);
			if (ISQL_errmsg(fbStatus))
			{
				break;
			}

			// Check for warnings.
			ISQL_warning(fbStatus);
		}
	} // while (!done)

	if (done)
	{
		// Save whatever we were able to pump, except when the failure was the commit itself.
		if (M__trans)
		{
			if (commit_failedM)
			{
				M__trans->rollback(fbStatus);
				if (succeeded())
					M__trans = NULL;
			}
			else
				commit_trans(&M__trans);
		}

		if (D__trans)
		{
			if (commit_failedD)
			{
				D__trans->rollback(fbStatus);
				if (succeeded())
					D__trans = NULL;
			}
			else
				commit_trans(&D__trans);
		}

		TEXT errbuf[MSG_LENGTH];
		sprintf(errbuf, "Stopped prematurely due to error in line %d with text:",
			Filelist->Ifp().indev_aux);
		STDERROUT(errbuf);
		STDERROUT(lastInputLine);
		STDERROUT("Going to EOF");
		// Avoid thousands of errors. Assume the file is full of bulk insertion data.
		Filelist->gotoEof();

		ret = ps_ERR;
	}

	// Get rid of the statement handle.
	global_Stmt->free(fbStatus);
	if (succeeded())
		global_Stmt = NULL;

	// Statistics printed here upon request
	if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
		ret = ps_ERR;

	return ret;
}


// *****************************************
// b u l k _ i n s e r t _ r e t r i e v e r
// *****************************************
// Helper to the previous bulk_insert_hack to get more lines of input.
// It returns true when it finds end of file or too long string (almost 64K).
static bool bulk_insert_retriever(const char* prompt)
{
	readNextInputLine(prompt);
	getColumn = -1; // We are bypassing getNextInputChar().

	// Stop at end of line only. Empty lines are valid in this mode if inside quoted strings
	// but this function cannot know whether we are in a string because it doesn't parse
	// but only retrieves another line. Therefore, assumes and empty line is valid.
	bool rc = false;
	if (lastInputLine == NULL)
		rc = true;
	else
	{
		size_t length = strlen(lastInputLine);
		/*
		if (length == 0)
			rc = true;
		*/
		if (length > MAX_SQL_LENGTH)
		{
			TEXT msg[MSG_LENGTH];
			IUTILS_msg_get(BUFFER_OVERFLOW, msg);
			STDERROUT(msg);
			rc = true;
		}
	}

	return rc;
}


// *******************
// c h e c k _ d a t e
// *******************
// Check date as entered by the user, before adjustment (year - 1900, month - 1).
static bool check_date(const tm& times)
{
	const int y = times.tm_year;
	const int m = times.tm_mon;
	const int d = times.tm_mday;
	if (y < 1 || y > 4999)
		return false;
	if (m < 1 || m > 12)
		return false;
	const bool leap = y % 4 == 0 && y % 100 != 0 || y % 400 == 0;
	const int days[] = {0, 31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
	if (d < 1 || d > days[m])
		return false;
	return true;
}


// *******************
// c h e c k _ t i m e
// *******************
// Check time is in range.
static bool check_time(const tm& times)
{
	if (times.tm_hour < 0 || times.tm_hour > 23)
		return false;
	if (times.tm_min < 0 || times.tm_min > 59)
		return false;
	if (times.tm_sec < 0 || times.tm_sec > 59)
		return false;
	return true;
}


// ******************************
// c h e c k _ t i m e s t a  m p
// ******************************
// Check both date and time according to the previous functions
// and also check milliseconds range.
static bool check_timestamp(const tm& times, const int msec)
{
	return check_date(times) && check_time(times) && msec >= 0 && msec <= 9999;
}


// *************
// c h o p _ a t
// *************
// Simply ensure a given string argument fits in a size, terminator included.
static size_t chop_at(char target[], const size_t size)
{
	size_t len = strlen(target);
	if (len >= size)
	{
		len = size - 1;
		target[len] = 0;
	}
	return len;
}


static void col_check(const TEXT* tabname, unsigned* colnumber)
{
/**************************************
 *
 *	c o l _ c h e c k
 *
 **************************************
 *
 *	Check for peculiarities not currently revealed by the SQLDA
 *	colnumber array records the mapping of select columns to insert
 *	columns which do not have an equivalent for array or computed cols.
 **************************************/

	if (!frontendTransaction())
		return;

	// Query to get array info and computed source not available in the sqlda
	int i = 0, j = 0;
	FOR F IN RDB$FIELDS CROSS
		R IN RDB$RELATION_FIELDS WITH
		F.RDB$FIELD_NAME = R.RDB$FIELD_SOURCE AND
		R.RDB$RELATION_NAME EQ tabname
		SORTED BY R.RDB$FIELD_POSITION, R.RDB$FIELD_NAME

		if ((!F.RDB$DIMENSIONS.NULL && F.RDB$DIMENSIONS) || (!F.RDB$COMPUTED_BLR.NULL))
			colnumber[i] = ~0u;
		else
			colnumber[i] = j++;
		++i;
	END_FOR
	ON_ERROR
		ISQL_errmsg(fbStatus);
	END_ERROR;
}


static processing_state copy_table(TEXT* source,
						 TEXT* destination,
						 TEXT* otherdb)
{
/**************************************
 *
 *	c o p y _ t a b l e
 *
 **************************************
 *
 * Functional description
 *	Create a new table based on an existing one.
 *
 *	Parameters:  source -- name of source table
 *			 destination == name of newly created table
 *
 **************************************/
	if (!source[0] || !destination[0])
	{
		STDERROUT("Either source or destination tables are missing");
		return SKIP;
	}

	TEXT errbuf[MSG_LENGTH];

	// Call list_table with a temporary file, then hand that file to a
	// new version of isql

	FILE* const holdout = isqlGlob.Out;

	// If there is an alternate database, extract the domains
	const bool domain_flag = otherdb[0];

	const Firebird::PathName ftmp = TempFile::create(SCRATCH);
	isqlGlob.Out = fopen(ftmp.c_str(), "w+b");
	if (!isqlGlob.Out)
	{
		// If we can't open a temp file then bail

		IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << ftmp.c_str());
		STDERROUT(errbuf);
		Exit_value = FINI_ERROR;
		isqlGlob.Out = holdout;
		return END;
	}

	chop_at(source, QUOTEDLENGTH);
	if (source[0] != DBL_QUOTE)
		IUTILS_make_upper(source);
	/*
	chop_at(source_tbl, WORDLENGTH);
	TEXT source[QUOTEDLENGTH];
	bool delimited_yes = source_tbl[0] == DBL_QUOTE;
	if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) {
		IUTILS_copy_SQL_id(source_tbl, source, DBL_QUOTE);
	}
	else
	{
		strcpy(source, source_tbl);
		IUTILS_make_upper(source);
	}
	*/

	chop_at(destination, QUOTEDLENGTH);
	if (destination[0] != DBL_QUOTE)
		IUTILS_make_upper(destination);
	/*
	chop_at(destination_tbl, WORDLENGTH);
	TEXT destination[QUOTEDLENGTH];
	delimited_yes = destination_tbl[0] == DBL_QUOTE;
	if (isqlGlob.db_SQL_dialect > SQL_DIALECT_V6_TRANSITION && delimited_yes) {
		IUTILS_copy_SQL_id(destination_tbl, destination, DBL_QUOTE);
	}
	else
	{
		strcpy(destination, destination_tbl);
		IUTILS_make_upper(destination);
	}
	*/

	if (EXTRACT_list_table(source, destination, domain_flag, -1))
	{
		IUTILS_msg_get(NOT_FOUND_MSG, errbuf, SafeArg() << source);
		STDERROUT(errbuf);
		fclose(isqlGlob.Out);
	}
	else
	{
		fclose(isqlGlob.Out);

		// easy to make a copy in another database
		const TEXT* altdb = isqlGlob.global_Db_name;
		if (*otherdb)
			altdb = otherdb;
		TEXT cmd[MAXPATHLEN * 2 + 20];
		sprintf(cmd, "isql -q %s -i %s", altdb, ftmp.c_str());
		if (system(cmd))
		{
			IUTILS_msg_get(COPY_ERR, errbuf, SafeArg() << destination << altdb);
			STDERROUT(errbuf);
		}
	}

	unlink(ftmp.c_str());
	isqlGlob.Out = holdout;

	return (SKIP);
}


static void appendClause(Firebird::string& to, const char* label, const TEXT* value, char quote = 0)
{
	to += ' ';
	to += label;
	to += ' ';
	if (quote)
		to += quote;
	to += value;
	if (quote)
		to += quote;
	to += ' ';
}


static processing_state create_db(const TEXT* statement, TEXT* d_name)
{
/**************************************
 *
 *	c r e a t e _ d b
 *
 **************************************
 *
 * Functional description
 *	Intercept create database commands to
 *	adjust the DB and transaction handles
 *
 *	Parameters:  statement == the entire statement for processing.
 *
 * Note: SQL ROLE setting must be taken into an account no matter
 *	that the newly created database will not have any roles defined
 *	in it. Role may affect right to create new database.
 *
 **************************************/
	processing_state ret = SKIP;

	// Disconnect from the database and cleanup
	ISQL_disconnect_database(false);

	// Parse statement to tokens
	const char* quotes = "\"'";
	Firebird::string nlStatement(statement);
	nlStatement += '\n';
	Firebird::Tokens toks;
	toks.quotes(quotes);
	toks.parse(0, nlStatement.c_str());

	const unsigned KEY_USER = 0;
	const unsigned KEY_PASS = 1;
	const unsigned KEY_ROLE = 2;
	const unsigned KEY_NAMES = 3;
	const unsigned KEY_SET = 4;

	struct Key
	{
		const char* text;
		bool has;
	};

	Key keys[5] = {
		{ "USER", false },
		{ "PASSWORD", false },
		{ "ROLE", false },
		{ "NAMES", false },
		{ "SET", false }
	};

	for (unsigned t = 0; t < toks.getCount(); ++t)
	{
		Firebird::NoCaseString token(toks[t].text, toks[t].length);
		unsigned k;

		for (k = 0; k < FB_NELEM(keys); ++k)
		{
			if (token == keys[k].text)
			{
				if (k != KEY_NAMES || keys[KEY_SET].has)
					keys[k].has = true;
				break;
			}
		}

		if (k != KEY_SET)
			keys[KEY_SET].has = false;
	}

	for (int createWithoutRole = 0; createWithoutRole < 2; ++createWithoutRole)
	{
		ret = SKIP;
		TEXT usr[USER_LENGTH];
		TEXT psw[PASSWORD_LENGTH];
		TEXT role[ROLE_LENGTH];

		Firebird::string modifiedCreateStatement(statement);

		TEXT quote = DBL_QUOTE;
		const TEXT* p = NULL;

		// If there is a global parameter, we will set it into the create stmt.
		if (global_usr || global_role || global_psw ||
			(*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)))
		{
			strcpy(usr, isqlGlob.User);
			strip_quotes(Password, psw);
			strcpy(role, isqlGlob.Role);

			Firebird::string clauses;

			if (global_usr && !keys[KEY_USER].has)
				appendClause(clauses, keys[KEY_USER].text, usr);

			if (global_psw && !keys[KEY_PASS].has)
				appendClause(clauses, keys[KEY_PASS].text, psw, SINGLE_QUOTE);

			if (global_role && (!keys[KEY_ROLE].has) && createWithoutRole == 0)
				appendClause(clauses, keys[KEY_ROLE].text, role);

			if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)&& !keys[KEY_NAMES].has)
			{
				Firebird::string setNames = keys[KEY_SET].text;
				setNames += ' ';
				setNames += keys[KEY_NAMES].text;
				appendClause(clauses, setNames.c_str(), setValues.ISQL_charset, SINGLE_QUOTE);
			}

			if (toks.getCount() > 3)
				modifiedCreateStatement.insert(toks[3].origin, clauses);
			else
				modifiedCreateStatement += clauses;
		}

		// execute the create statement
		// If the isqlGlob.SQL_dialect is not set or set to 2, create the database
		// as a dialect 3 database.
		unsigned dialect =
			(isqlGlob.SQL_dialect == 0 || isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION) ?
				requested_SQL_dialect : isqlGlob.SQL_dialect;

		DB = Firebird::UtilInterfacePtr()->executeCreateDatabase(fbStatus, modifiedCreateStatement.length(),
			modifiedCreateStatement.c_str(), dialect, NULL);

		if ((!DB) && (createWithoutRole == 0) && (fbStatus->getErrors()[1] == isc_dsql_error))
		{
			// OLd server failed to parse ROLE clause
			continue;
		}

		if (ISQL_errmsg(fbStatus))
		{
			ret = FAIL;
		}
		break;
	}

	if (DB)
	{
		// Make it read owner name to display grantor correctly
		SHOW_read_owner();

		// No use in cancel when running non-end-user operators
		DB->cancelOperation(fbStatus, fb_cancel_disable);

		// Load isqlGlob.global_Db_name with some value to show a successful attach

		// CVC: Someone may decide to play strange games with undocumented ability
		// to write crap between CREATE DATABASE and the db name, as described by
		// Helen on CORE-932. Let's see if we can discover the real db name.
		Firebird::string s;
		if (toks.getCount() > 2)
			s.assign(toks[2].text, toks[2].length);
		else
			s = d_name;
		strip_quotes(s.c_str(), isqlGlob.global_Db_name);

		ISQL_get_version(true);

		// Start the user transaction

		if (!M__trans)
		{
			M_Transaction();
			if (D__trans)
				commit_trans(&D__trans);
			if (setValues.Autocommit)
				D_Transaction();
		}

	}

	return (ret);
}


static void do_isql()
{
/**************************************
 *
 *	d o _ i s q l
 *
 **************************************
 *
 * Functional description
 *	Process incoming SQL statements, using the global message metadata
 *
 **************************************/
	TEXT errbuf[MSG_LENGTH];

	// Initialized user transactions

	M__trans = 0;

#if defined(_MSC_VER) && _MSC_VER >= 1400 && _MSC_VER < 1900
	_set_output_format(_TWO_DIGIT_EXPONENT);
#endif

#ifdef WIN_NT
	SetConsoleCtrlHandler(query_abort, TRUE);
#else
	fb_shutdown_callback(0, query_abort, fb_shut_confirmation, 0);
#endif

	// Open database and start tansaction

	//
	// We will not execute this for now on WINDOWS. We are not prompting for
	// a database name, username and password. A connect statement has to be in
	// the file containing the script.

	newdb(isqlGlob.global_Db_name, isqlGlob.User, Password, global_numbufs, isqlGlob.Role, true);

	// If that failed or no Dbname was specified

	ISQL_dbcheck();

	// Read statements and process them from Ifp until the ret
	// code tells us we are done

	Firebird::string stmt;
	processing_state ret;

	bool done = false;
	while (!done)
	{

		if (Abort_flag)
		{
			if (D__trans)
			{
				D__trans->rollback(fbStatus);
				if (succeeded())
					D__trans = NULL;
			}

			if (M__trans)
			{
				M__trans->rollback(fbStatus);
				if (succeeded())
					M__trans = NULL;
			}

			if (fbTrans)
			{
				fbTrans->rollback(fbStatus);
				if (succeeded())
					fbTrans = NULL;
			}

			// If there is current user statement, free it
			if (global_Stmt)
			{
				global_Stmt->free(fbStatus);
				if (succeeded())
					global_Stmt = NULL;
			}

			if (DB)
			{
				DB->detach(fbStatus);
				if (succeeded())
					DB = NULL;
			}

			break;
		}

		if (Interrupt_flag)
		{
			// SIGINT caught in interactive mode
			Interrupt_flag = false;

			if (Input_file)
			{
				// close input files going back to stdin
				Filelist->clear(stdin);

				// should have two stdin in Filelist
				fb_assert(Filelist->count() == 2);

				Filelist->removeIntoIfp();
				Input_file = false;
			}
		}

		ret = get_statement(stmt, sql_prompt);

		// If there is no database yet, remind us of the need to connect

		// But don't execute the statement

		if (!isqlGlob.global_Db_name[0] && (ret == CONT))
		{
			if (!Quiet)
			{
				IUTILS_msg_get(NO_DB, errbuf);
				STDERROUT(errbuf);
			}
			if (!Interactive && setValues.BailOnError)
				ret = FAIL;
			else
				ret = SKIP;
		}

		switch (ret)
		{
		case CONT:
			if (process_statement(stmt.c_str()) == ps_ERR)
			{
				Exit_value = FINI_ERROR;
				if (!Interactive && setValues.BailOnError)
					Abort_flag = true;
			}
			break;

		case END:
		case EOF:
		case EXIT:
			if (Abort_flag)
			{
				if (D__trans)
				{
					D__trans->rollback(fbStatus);
					if (succeeded())
						D__trans = NULL;
				}

				if (M__trans)
				{
					M__trans->rollback(fbStatus);
					if (succeeded())
						M__trans = NULL;
				}

				if (fbTrans)
				{
					fbTrans->rollback(fbStatus);
					if (succeeded())
						fbTrans = NULL;
				}
			}
			else
			{
				if (D__trans)
					commit_trans(&D__trans);
				if (M__trans)
					commit_trans(&M__trans);
				if (fbTrans)
					commit_trans(&fbTrans);
			}

			// If there is current user statement, free it
			// I think DSQL_drop is the right one, but who knows
			if (global_Stmt)
			{
				global_Stmt->free(fbStatus);
				if (succeeded())
					global_Stmt = NULL;
			}

			if (DB)
			{
				DB->detach(fbStatus);
				if (succeeded())
					DB = NULL;
			}

			done = true;
			break;

		case BACKOUT:
			if (D__trans)
			{
				D__trans->rollback(fbStatus);
				if (succeeded())
					D__trans = NULL;
			}

			if (M__trans)
			{
				M__trans->rollback(fbStatus);
				if (succeeded())
					M__trans = NULL;
			}

			if (fbTrans)
			{
				fbTrans->rollback(fbStatus);
				if (succeeded())
					fbTrans = NULL;
			}

			// If there is current user statement, free it
			if (global_Stmt)
			{
				global_Stmt->free(fbStatus);
				if (succeeded())
					global_Stmt = NULL;
			}

			if (DB)
			{
				DB->detach(fbStatus);
				if (succeeded())
					DB = NULL;
			}

			done = true;
			break;

		case ERR_BUFFER_OVERFLOW:
			IUTILS_msg_get(BUFFER_OVERFLOW, errbuf);
			STDERROUT(errbuf);

		case EXTRACT:
		case EXTRACTALL:
		default:
			// fb_assert (FALSE);  -- removed as finds too many problems
		case ps_ERR:
		case FAIL:
			Exit_value = FINI_ERROR;
			if (!Interactive && setValues.BailOnError)
				Abort_flag = true;
			break;

		case SKIP:
			break;
		}
	}

	global_Stmt = NULL;
	DB = NULL;
	isqlGlob.global_Db_name[0] = '\0';
	D__trans = NULL;
	M__trans = NULL;
	fbTrans = NULL;

	InputDevices::indev& Ofp = Filelist->Ofp();
	// Does it have a valid Temp file pointer?
	if (Ofp.indev_fpointer)
		Ofp.drop();

	// CVC: If we were halt by an error and Bail, we have pending cleanup.
	Filelist->clear();
	if (lastInputLine)
		free(lastInputLine);

	setValues.global_Cols.clear(); // The destructor would do anyway if we don't reach this point.
}


static processing_state drop_db()
{
/**************************************
 *
 *	d r o p _ d b
 *
 **************************************
 *
 * Functional description
 *	Drop the current database
 *
 **************************************/
	if (DB && isqlGlob.global_Db_name[0])
	{
		RELEASE_REQUESTS FOR DB;

		DB->dropDatabase(fbStatus);
		if (ISQL_errmsg(fbStatus))
		{
			return (FAIL);
		}
	}
	else
		return (FAIL);

	// The database got dropped with or without errors
	M__trans = NULL;
	fbTrans = NULL;
	global_Stmt = NULL;
	D__trans = NULL;

	// CVC: If we aren't connected to a db anymore, then the db's dialect is reset.
	// This should fix SF Bug #910430.
	isqlGlob.db_SQL_dialect = 0;
	// BRS this is also needed to fix #910430.
	global_dialect_spoken = 0;

	// Zero database name

	isqlGlob.global_Db_name[0] = '\0';
	DB = NULL;

	return (SKIP);
}


static processing_state edit(const TEXT* const* cmd)
{
/**************************************
 *
 *	e d i t
 *
 **************************************
 *
 * Functional description
 *	Edit the current file or named file
 *
 *	Parameters:  cmd -- Array of words interpreted as file name
 *	The result of calling this is to point the global input file
 *	pointer, Ifp, to the named file after editing or the tmp file.
 *
 **************************************/

	// Set up editing command for shell

	const TEXT* file = cmd[1];

	// If there is a file name specified, try to open it

	processing_state rc = SKIP;
	if (*file)
	{
		TEXT path[MAXPATHLEN];
		strip_quotes(file, path);
		FILE* fp = fopen(path, "r");
		if (fp)
		{
			// Push the current ifp on the indev

			Filelist->insertIfp();
			Filelist->Ifp().init(fp, path, path);
			gds__edit(path, 0);
			Input_file = true;
			getColumn = -1;
		}
		else
		{
			TEXT errbuf[MSG_LENGTH];
			IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << path);
			STDERROUT(errbuf);
			rc = ps_ERR;
		}
	}
	else
	{
		// No file given, edit the temp file

		Filelist->insertIfp();
		// Close the file, edit it, then reopen and read from the top
		InputDevices::indev& Ofp = Filelist->Ofp();

		if (!Ofp.indev_fpointer)
		{
			// File used to edit sessions
			const Firebird::PathName filename = TempFile::create(SCRATCH);
			const char* Tmpfile = filename.c_str();
			FILE* f = fopen(Tmpfile, "w+"); // It was w+b
			if (f)
			{
				Ofp.init(f, Tmpfile, Tmpfile);
				Filelist->commandsToFile(f);
			}
			else
			{
				// If we can't open a temp file then bail
				TEXT errbuf[MSG_LENGTH];
				IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << Tmpfile);
				STDERROUT(errbuf);
				return ps_ERR;
			}
		}

		Ofp.close();
		PathName tmpfile = Ofp.fileName(false);
		gds__edit(tmpfile.c_str(), 0);
		Ofp.init(fopen(tmpfile.c_str(), "r+"), tmpfile.c_str(), tmpfile.c_str()); // We don't check for failure.
		Filelist->Ifp().init(Ofp);
		Input_file = true;
		getColumn = -1;
	}

	return rc;
}


static processing_state end_trans()
{
/**************************************
 *
 *	e n d _ t r a n s
 *
 **************************************
 *
 * Functional description
 *	Prompt the interactive user if there is an extant transaction and
 *	either commit or rollback
 *
 *	Called by newtrans, createdb, newdb;
 *	Returns success or failure.
 *
 **************************************/

	TEXT infobuf[BUFFER_LENGTH60];

	processing_state ret = CONT;
	// Give option of committing or rolling back before proceding unless
	// the last command was a commit or rollback

	if (M__trans)
	{
		if (Interactive)
		{
			IUTILS_msg_get(COMMIT_PROMPT, sizeof(infobuf), infobuf);
			readNextInputLine(infobuf);
			getColumn = -1; // We are bypassing getNextInputChar().
			if (lastInputLine && isyesno(lastInputLine))
			{
				// check for Yes answer
				IUTILS_msg_get(COMMIT_MSG, sizeof(infobuf), infobuf);
				STDERROUT(infobuf);
				if (DB && M__trans)
				{
					M__trans->commit(fbStatus);
					if (ISQL_errmsg(fbStatus))
					{
						// Commit failed, so roll back anyway
						ret = FAIL;
					}
					else
						M__trans = NULL;
				}
			}
			else
			{
				IUTILS_msg_get(ROLLBACK_MSG, sizeof(infobuf), infobuf);
				STDERROUT(infobuf);
				if (DB && M__trans)
				{
					M__trans->rollback(fbStatus);
					if (ISQL_errmsg(fbStatus))
						ret = FAIL;
					else
						M__trans = NULL;
				}
			}
		}
		else
		{
			// No answer, just roll back by default

			if (DB && M__trans)
			{
				// For WISQL, we keep track of whether a commit is needed by setting a flag in the ISQLPB
				// structure.  This flag is set whenever a sql command is entered in the SQL input area or
				// if the user uses the create database dialog.  Because of this, this should only show up if
				// the user connects and then disconnects or if the user enters a SET TRANSACTION stat without
				// ever doing anything that would cause changes to the dictionary.
				IUTILS_msg_get(ROLLBACK_MSG, sizeof(infobuf), infobuf);
				STDERROUT(infobuf);
				M__trans->rollback(fbStatus);
				if (ISQL_errmsg(fbStatus))
					ret = FAIL;
				else
					M__trans = NULL;
			}
		}
	}

	// Commit background transaction

	if (DB && D__trans)
	{
		D__trans->commit(fbStatus);
		if (ISQL_errmsg(fbStatus))
			ret = FAIL;
		else
			D__trans = NULL;
	}
	return ret;
}


static processing_state escape(const TEXT* cmd)
{
/**************************************
 *
 *	e s c a p e
 *
 **************************************
 *
 * Functional description
 *	Permit a shell escape to system call
 *
 *	Parameters:  cmd -- The command string with SHELL
 *
 **************************************/

	// Advance past the shell

	const TEXT* shellcmd = cmd;

	// Search past the 'shell' keyword
	shellcmd += strlen("shell");

	// Eat whitespace at beginning of command
	while (*shellcmd && fb_isspace(*shellcmd))
		shellcmd++;

#ifdef WIN_NT
	// MSDN says: You must explicitly flush (using fflush or _flushall)
	// or close any stream before calling system.
	// CVC: But that function defeats our possible several input streams opened.
	//_flushall();
	// Save Ofp position in case it's being used as input. See EDIT command.
	fpos_t OfpPos = 0;
	if (Filelist->Ofp().indev_fpointer)
		Filelist->Ofp().getPos(&OfpPos);
	fflush(NULL); // Flush only output buffers.
	const char* emptyCmd = "%ComSpec%";
#else
	const char* emptyCmd = "$SHELL";
#endif

	// If no command given just spawn a shell
	if (!*shellcmd)
		shellcmd = emptyCmd;

	const int rc = system(shellcmd);

#ifdef WIN_NT
	// If we are reading from the temp file, restore the read position because
	// it's opened in r+ mode in this case, that's R/W.
	if (Filelist->sameInputAndOutput())
		Filelist->Ofp().setPos(&OfpPos);
#endif

	return rc ? FAIL : SKIP;
}


static processing_state frontend(const TEXT* statement)
{
/**************************************
 *
 *	f r o n t e n d
 *
 **************************************
 *
 * Functional description
 *	Handle any frontend commands that start with
 *	show or set converting the string into an
 *	array of words parms, with MAX_TERMS words only.
 *
 *	Parameters: statement is the string typed by the user
 *
 **************************************/

	class FrontOptions : public OptionsBase
	{
	public:
		enum front_commands
		{
			show, add, copy,
			blobview, output, shell, set, create, drop, connect,
			edit, input, quit, exit, help,
#ifdef DEV_BUILD
			passthrough,
#endif
			wrong
		};
		FrontOptions(const optionsMap* inmap, size_t insize, int wrongval)
			: OptionsBase(inmap, insize, wrongval)
		{}
	};

	static const FrontOptions::optionsMap options[] =
	{
		{FrontOptions::show, "SHOW", 0},
		{FrontOptions::add, "ADD", 0},
		{FrontOptions::copy, "COPY", 0},
		{FrontOptions::blobview, "BLOBVIEW", 0},
		{FrontOptions::blobview, "BLOBDUMP", 0},
		//{FrontOptions::output, "OUT", },
		{FrontOptions::output, "OUTPUT", 3},
		{FrontOptions::shell, "SHELL", 0},
		{FrontOptions::set, "SET", 0},
		{FrontOptions::create, "CREATE", 0},
		{FrontOptions::drop, "DROP", 0},
		{FrontOptions::connect, "CONNECT", 0},
		{FrontOptions::edit, "EDIT", 0},
		//{FrontOptions::input, "IN", },
		{FrontOptions::input, "INPUT", 2},
		{FrontOptions::quit, "QUIT", 0},
		{FrontOptions::exit, "EXIT", 0},
		{FrontOptions::help, "?", 0},
		{FrontOptions::help, "HELP", 0}
#ifdef DEV_BUILD
		,
		{FrontOptions::passthrough, "PASSTHROUGH", 0}
#endif
	};

	TEXT errbuf[MSG_LENGTH];

	// Store the first NUM_TERMS words as they appear in parms, using blanks
	// to delimit.   Each word beyond a real word gets a null char
	// Shift parms to upper case, leaving original case in lparms
	typedef TEXT* isql_params_t[MAX_TERMS];
	isql_params_t parms, lparms;
	for (int iter = 0; iter < FB_NELEM(lparms); ++iter)
	{
		lparms[iter] = NULL;
		parms[iter] = NULL;
	}
	TEXT parm_defaults[MAX_TERMS][1];

	// Any whitespace and comments at the beginning are already swallowed by get_statement()

	// Set beginning of statement past comment
	const TEXT* const cmd = statement;
	if (!*cmd)
	{
		// In case any default transaction was started - commit it here
		if (fbTrans)
			commit_trans(&fbTrans);

		return SKIP;
	}

	frontend_load_parms(statement, parms, lparms, parm_defaults);

    char bad_dialect_buf[512];
	bool bad_dialect = false;

	// Look to see if the words (parms) match any known verbs. If nothing
	// matches then just hand the statement to process_statement
	processing_state ret = SKIP;
	const FrontOptions frontoptions(options, FB_NELEM(options), FrontOptions::wrong);
	switch (frontoptions.getCommand(parms[0]))
	{
	case FrontOptions::show:
		if (DB && !frontendTransaction())
		{
			// Free the frontend command
			frontend_free_parms(parms, lparms, parm_defaults);
			return FAIL;
		}

		ret = SHOW_metadata(parms, lparms);
		if (fbTrans)
			commit_trans(&fbTrans);
		break;

	case FrontOptions::add:
		if (!frontendTransaction())
		{
			// Free the frontend command
			frontend_free_parms(parms, lparms, parm_defaults);
			return FAIL;
		}

		ret = add_row(lparms[1]);
		if (fbTrans)
			commit_trans(&fbTrans);
		break;

	case FrontOptions::copy:
		if (!frontendTransaction())
		{
			// Free the frontend command
			frontend_free_parms(parms, lparms, parm_defaults);
			return FAIL;
		}

		ret = copy_table(lparms[1], lparms[2], lparms[3]);
		if (fbTrans)
			commit_trans(&fbTrans);
		break;

	case FrontOptions::blobview:
		ret = blobedit(parms[0], lparms);
		break;

	case FrontOptions::output:
		ret = newoutput(lparms[1]);
		break;

	case FrontOptions::shell:
		ret = escape(cmd);
		break;

	case FrontOptions::set:
		ret = frontend_set(cmd, parms, lparms, bad_dialect_buf, bad_dialect);
		break;

	case FrontOptions::create:
		if (!strcmp(parms[1], "DATABASE") ||
			(!strcmp(parms[1], "SCHEMA") && isqlGlob.major_ods > 0 && isqlGlob.major_ods < ODS_VERSION12))
		{
			ret = create_db(cmd, lparms[2]);
		}
		else
			ret = CONT;
		break;

	case FrontOptions::drop:
		if (!strcmp(parms[1], "DATABASE") ||
			(!strcmp(parms[1], "SCHEMA") && isqlGlob.major_ods > 0 && isqlGlob.major_ods < ODS_VERSION12))
		{
			if (*parms[2])
				ret = ps_ERR;
			else
				ret = drop_db();
		}
		else
			ret = CONT;
		break;

	case FrontOptions::connect:
		{
			const TEXT* psw = NULL;
			const TEXT* usr = NULL;
			const TEXT* sql_role_nm = NULL;
			int numbufs = 0;

			// if a parameter is given in the command more than once, the
			// last one will be used. The parameters can appear each any
			// order, but each must provide a value.

			ret = SKIP;
			for (int i = 2; i < (MAX_TERMS - 1);)
			{
				if (!strcmp(parms[i], "CACHE") && *lparms[i + 1])
				{
					char* err;
					long value = strtol(lparms[i + 1], &err, 10);
					if (*err || (value <= 0) || (value >= INT_MAX))
					{
						ret = ps_ERR;
						break;
					}
					numbufs = (int) value;
					i += 2;
				}
				else if (!strcmp(parms[i], "USER") && *lparms[i + 1])
				{
					usr = lparms[i + 1];
					i += 2;
				}
				else if (!strcmp(parms[i], "PASSWORD") && *lparms[i + 1])
				{
					psw = lparms[i + 1];
					i += 2;
				}
				else if (!strcmp(parms[i], "ROLE") && *lparms[i + 1])
				{
					sql_role_nm = lparms[i + 1];
					i += 2;
				}
				else if (*parms[i])
				{
					// Unrecognized option to CONNECT
					ret = ps_ERR;
					break;
				}
				else
					i++;
			}
			if (ret != ps_ERR)
				ret = newdb(lparms[1], usr, psw, numbufs, sql_role_nm, true);
		}
		break;

	case FrontOptions::edit:
		ret = edit(lparms);
		break;

	case FrontOptions::input:
		// CVC: Set by newinput() below only if successful.
		//Input_file = true;
		ret = newinput(lparms[1]);
		break;

	case FrontOptions::quit:
		ret = BACKOUT;
		break;

	case FrontOptions::exit:
		ret = EXIT;
		break;

	case FrontOptions::help:
		ret = help(parms[1]);
		break;

#ifdef DEV_BUILD
	case FrontOptions::passthrough:
		ret = passthrough(cmd + 11);
		break;
#endif

	default:						// Didn't match, it must be SQL
		ret = CONT;
		break;
	}

	// In case any default transaction was started - commit it here
	if (fbTrans)
		commit_trans(&fbTrans);

	// Free the frontend command
	frontend_free_parms(parms, lparms, parm_defaults);

	if (ret == ps_ERR)
	{
		if (bad_dialect)
			IUTILS_msg_get(CMD_ERR, errbuf, SafeArg() << bad_dialect_buf);
		else
			IUTILS_msg_get(CMD_ERR, errbuf, SafeArg() << cmd);
		STDERROUT(errbuf);
	}

	return ret;
}


static void frontend_free_parms(TEXT* parms[], TEXT* lparms[], TEXT parm_defaults[][1])
{
	for (int j = 0; j < MAX_TERMS; j++)
	{
		if (parms[j] && parms[j] != parm_defaults[j])
		{
			ISQL_FREE(parms[j]);
			ISQL_FREE(lparms[j]);
		}
	}
}


static void frontend_load_parms(const TEXT* p, TEXT* parms[], TEXT* lparms[],
	TEXT parm_defaults[][1])
{
	TEXT buffer[BUFFER_LENGTH256];

	for (int i = 0; i < MAX_TERMS; ++i)
	{
		if (!*p)
		{
			parms[i] = lparms[i] = parm_defaults[i];
			parm_defaults[i][0] = '\0';
			continue;
		}

	    bool role_found = false;
		TEXT* a = buffer;
		int j = 0;
		const bool quoted = *p == DBL_QUOTE || *p == SINGLE_QUOTE;
		if (quoted)
		{
			if (i > 0 && (!strcmp(parms[i - 1], "ROLE")))
				role_found = true;
			bool delimited_done = false;
			const TEXT end_quote = *p;
			j++;
			*a++ = *p++;
			// Allow a quoted string to have embedded spaces
			//  Prevent overflow
			while (*p && !delimited_done && j < BUFFER_LENGTH256 - 1)
			{
				if (*p == end_quote)
				{
					j++;
					*a++ = *p++;
					if (*p && *p == end_quote && j < BUFFER_LENGTH256 - 1)
					{
						j++;	// do not skip the escape quote here
						*a++ = *p++;
					}
					else
						delimited_done = true;
				}
				else
				{
					j++;
					*a++ = *p++;
				}
			}
			*a = '\0';
		}
		else
		{
			//  Prevent overflow. Do not copy the string (redundant).
			while (*p && !fb_isspace(*p) && j < BUFFER_LENGTH256 - 1)
			{
				j++;
				++p;
			}
		}
		fb_assert(!quoted || strlen(buffer) == size_t(j));
		const size_t length = quoted ? strlen(buffer) : j;
		parms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1));
		lparms[i] = (TEXT*) ISQL_ALLOC((SLONG) (length + 1));
		memcpy(parms[i], quoted ? buffer : p - j, length);
		parms[i][length] = 0;
		while (*p && fb_isspace(*p))
			p++;
		strcpy(lparms[i], parms[i]);
		if (!role_found)
			IUTILS_make_upper(parms[i]);
	}
}


// ***********************
// f r o n t e n d _ s e t
// ***********************
// Validates and executes the SET {option {params}} command.
static processing_state frontend_set(const char* cmd, const char* const* parms,
	const char* const* lparms, char* const bad_dialect_buf, bool& bad_dialect)
{

	class SetOptions : public OptionsBase
	{
	public:
		enum set_commands
		{
			stat, count, list, plan, planonly, explain, blobdisplay, echo, autoddl,
			width, transaction, terminator, names, time,
//#ifdef DEV_BUILD
			sqlda_display,
//#endif
			sql, warning, sqlCont, heading, bail,
			bulk_insert, maxrows, keepTranParams, wrong
		};
		SetOptions(const optionsMap* inmap, size_t insize, int wrongval)
			: OptionsBase(inmap, insize, wrongval)
		{}
	};

	static const SetOptions::optionsMap options[] =
	{
		{SetOptions::stat, "STATS", 4},
		{SetOptions::count, "COUNT", 0},
		{SetOptions::list, "LIST", 0},
		{SetOptions::plan, "PLAN", 0},
		{SetOptions::planonly, "PLANONLY", 0},
		{SetOptions::explain, "EXPLAIN", 0},
		{SetOptions::blobdisplay, "BLOBDISPLAY", 4},
		{SetOptions::echo, "ECHO", 0},
		{SetOptions::autoddl, "AUTODDL", 4},
		{SetOptions::width, "WIDTH", 0},
		{SetOptions::transaction, "TRANSACTION", 5},
		{SetOptions::terminator, "TERMINATOR", 4},
		{SetOptions::names, "NAMES", 0},
		{SetOptions::time, "TIME", 0},
//#ifdef DEV_BUILD
		{SetOptions::sqlda_display, "SQLDA_DISPLAY", 0},
//#endif
		{SetOptions::sql, "SQL", 0},
		{SetOptions::warning, "WARNINGS", 7},
		{SetOptions::warning, "WNG", 0},
		{SetOptions::sqlCont, "GENERATOR", 0},
		{SetOptions::sqlCont, "STATISTICS", 0},
		{SetOptions::heading, "HEADING", 0},
		{SetOptions::bail, "BAIL", 0},
		{SetOptions::bulk_insert, "BULK_INSERT", 0},
		{SetOptions::maxrows, "ROWCOUNT", 0}, // legacy, compatible with FB2.5
		{SetOptions::maxrows, "MAXROWS", 0},
		{SetOptions::sqlCont, "ROLE", 0},
		{SetOptions::sqlCont, "TRUSTED", 0},	// TRUSTED ROLE, will get DSQL error other case
		{SetOptions::keepTranParams, "KEEP_TRAN_PARAMS", 9},
	};

	// Display current set options
	if (!*parms[1])
		return print_sets();

	processing_state ret = SKIP;
	const SetOptions setoptions(options, FB_NELEM(options), SetOptions::wrong);
	switch (setoptions.getCommand(parms[1]))
	{
	case SetOptions::sqlCont:
		ret = CONT;
		break;

	case SetOptions::stat:
		ret = do_set_command(parms[2], &setValues.Stats);
		break;

	case SetOptions::count:
		ret = do_set_command(parms[2], &setValues.Docount);
		break;

	case SetOptions::list:
		ret = do_set_command(parms[2], &setValues.List);
		break;

	case SetOptions::plan:
		ret = do_set_command(parms[2], &setValues.Plan);
		if (setValues.Planonly && !setValues.Plan)
			ret = do_set_command("OFF", &setValues.Planonly);
		break;

	case SetOptions::planonly:
		ret = do_set_command (parms[2], &setValues.Planonly);
		if (setValues.Planonly && !setValues.Plan)
		{
			// turn on plan
			ret = do_set_command ("ON", &setValues.Plan);
		}
		break;

	case SetOptions::explain:
		ret = do_set_command(parms[2], &setValues.ExplainPlan);
		if (setValues.ExplainPlan)
			ret = do_set_command("ON", &setValues.Plan);
		break;

	case SetOptions::blobdisplay:
		// No arg means turn off blob display
		if (!*parms[2] || !strcmp(parms[2], "OFF"))
			setValues.Doblob = NO_BLOBS;
		else if (!strcmp(parms[2], "ALL"))
			setValues.Doblob = ALL_BLOBS;
		else
			setValues.Doblob = atoi(parms[2]);
		break;

	case SetOptions::echo:
		ret = do_set_command(parms[2], &setValues.Echo);
		if (!setValues.Echo)
			ISQL_prompt("");
		break;

	case SetOptions::autoddl:
		ret = do_set_command(parms[2], &setValues.Autocommit);
		break;

	case SetOptions::width:
		ret = newsize(parms[2][0] == '"' ? lparms[2] : parms[2], parms[3]);
		break;

	case SetOptions::transaction:
		ret = newtrans(cmd);
		break;

	case SetOptions::terminator:
		{
			const TEXT* a = (*lparms[2]) ? lparms[2] : DEFTERM;
			for (size_t iter = 0; iter < sizeof(FORBIDDEN_TERM_CHARS); ++iter)
			{
				if (strchr(a, FORBIDDEN_TERM_CHARS[iter]))
				{
					TEXT msg_string[MSG_LENGTH];
					IUTILS_msg_get(INVALID_TERM_CHARS, msg_string, SafeArg() << FORBIDDEN_TERM_CHARS_DISPLAY);
					isqlGlob.printf("%s\n", msg_string);
					return ps_ERR;
				}
			}

			isqlGlob.Termlen = strlen(a);
			if (isqlGlob.Termlen < MAXTERM_SIZE)
			{
				strcpy(isqlGlob.global_Term, a);
			}
			else
			{
				isqlGlob.Termlen = MAXTERM_SIZE - 1;
				fb_utils::copy_terminate(isqlGlob.global_Term, a, isqlGlob.Termlen + 1);
			}
		}
		break;

	case SetOptions::names:
		if (!*parms[2])
		{
			const size_t lgth = strlen(DEFCHARSET);
			if (lgth < MAXCHARSET_SIZE)
				strcpy(setValues.ISQL_charset, DEFCHARSET);
			else
				fb_utils::copy_terminate(setValues.ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE);
		}
		else
		{
			const size_t lgth = strlen(parms[2]);
			if (lgth < MAXCHARSET_SIZE)
				strcpy(setValues.ISQL_charset, parms[2]);
			else
				fb_utils::copy_terminate(setValues.ISQL_charset, parms[2], MAXCHARSET_SIZE);
		}
		break;

	case SetOptions::time:
		ret = do_set_command(parms[2], &setValues.Time_display);
		break;

//#ifdef DEV_BUILD
	case SetOptions::sqlda_display:
		ret = do_set_command(parms[2], &setValues.Sqlda_display);
		break;
//#endif // DEV_BUILD

	case SetOptions::sql:
		if (!strcmp(parms[2], "DIALECT"))
			ret = get_dialect(parms[3], bad_dialect_buf, bad_dialect);
		else
			ret = ps_ERR;
		break;

	case SetOptions::warning:
		ret = do_set_command (parms[2], &setValues.Warnings);
		break;

	case SetOptions::heading:
		ret = do_set_command(parms[2], &setValues.Heading);
		break;

	case SetOptions::bail:
		ret = do_set_command(parms[2], &setValues.BailOnError);
		break;

	case SetOptions::bulk_insert:
		if (*parms[2])
			ret = bulk_insert_hack(cmd);
		else
			ret = ps_ERR;
		break;

	case SetOptions::maxrows:
		ret = newMaxRows((*lparms[2]) ? lparms[2] : "0");
		break;

	case SetOptions::keepTranParams:
		{
			const bool oldValue = setValues.KeepTranParams;
			ret = do_set_command(parms[2], &setValues.KeepTranParams);
			if ((ret != ps_ERR) && (oldValue != setValues.KeepTranParams))
				TranParams->assign(setValues.KeepTranParams ? DEFAULT_DML_TRANS_SQL : "");
		}

		break;

	default:
		{
			TEXT msg_string[MSG_LENGTH];
			IUTILS_msg_get(VALID_OPTIONS, msg_string);
			isqlGlob.printf("%s\n", msg_string);
		}
		setoptions.showCommands(isqlGlob.Out);
		ret = ps_ERR;
		break;
	}

	return ret;
}


static processing_state do_set_command(const TEXT* parm, bool* global_flag)
{
/**************************************
 *
 *	d o _ s e t _ c o m m a n d
 *
 **************************************
 *
 * Functional description
 *	set the flag pointed to by global_flag
 *		to true or false.
 *	if parm is missing, toggle it
 *	if parm is "ON", set it to true
 *	if parm is "OFF", set it to false
 *
 **************************************/
	processing_state ret = SKIP;

	if (!*parm)
		*global_flag = !*global_flag;
	else if (!strcmp(parm, "ON"))
		*global_flag = true;
	else if (!strcmp(parm, "OFF"))
		*global_flag = false;
	else
		ret = ps_ERR;
	return (ret);
}


// *********************
// g e t _ d i a l e c t
// *********************
// Validates SET SQL DIALECT command according to the target db.
static processing_state get_dialect(const char* const dialect_str,
	char* const bad_dialect_buf, bool& bad_dialect)
{
	processing_state ret = SKIP;
	bool print_warning = false;
	const USHORT old_SQL_dialect = isqlGlob.SQL_dialect; // save the old SQL dialect
	if (dialect_str && (isqlGlob.SQL_dialect = atoi(dialect_str)))
	{
		if (isqlGlob.SQL_dialect < SQL_DIALECT_V5 ||
			isqlGlob.SQL_dialect > SQL_DIALECT_V6)
		{
			bad_dialect = true;
			sprintf(bad_dialect_buf, "%s%s",
					"invalid SQL dialect ", dialect_str);
			isqlGlob.SQL_dialect = old_SQL_dialect;	// restore SQL dialect
			ret = ps_ERR;
		}
		else
		{
			if (isqlGlob.major_ods)
			{
				if (isqlGlob.major_ods < ODS_VERSION10)
				{
					if (isqlGlob.SQL_dialect > SQL_DIALECT_V5)
					{
						if (global_dialect_spoken)
						{
							sprintf(bad_dialect_buf,
									"%s%d%s%s%s%d%s",
									"ERROR: Database SQL dialect ",
									global_dialect_spoken,
									" database does not accept Client SQL dialect ",
									dialect_str,
									" setting. Client SQL dialect still remains ",
									old_SQL_dialect, NEWLINE);
						}
						else
						{
							sprintf(bad_dialect_buf,
									"%s%s%s%s%s%s",
									"ERROR: Pre IB V6 database only speaks ",
									"Database SQL dialect 1 and ",
									"does not accept Client SQL dialect ",
									dialect_str,
									" setting. Client SQL dialect still remains 1.",
									NEWLINE);
						}
						isqlGlob.SQL_dialect = old_SQL_dialect;	// restore SQL dialect
						isqlGlob.prints(bad_dialect_buf);
					}
				}
				else
				{
					// ODS 10 databases
					switch (global_dialect_spoken)
					{
					case SQL_DIALECT_V5:
						if (isqlGlob.SQL_dialect > SQL_DIALECT_V5)
						{
							if (SQL_DIALECT_V6_TRANSITION)
								Merge_stderr = true;
							print_warning = true;
						}
						break;
					case SQL_DIALECT_V6:
						if (isqlGlob.SQL_dialect == SQL_DIALECT_V5 ||
							isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION)
						{
							if (SQL_DIALECT_V6_TRANSITION)
								Merge_stderr = true;
							print_warning = true;
						}
						break;
					default:
						break;
					}
					if (print_warning && setValues.Warnings)
					{
						//print_warning = false;
						sprintf(bad_dialect_buf, "%s%d%s%d%s%s",
								"WARNING: Client SQL dialect has been set to ",
								isqlGlob.SQL_dialect,
								" when connecting to Database SQL dialect ",
								global_dialect_spoken,
								" database. ", NEWLINE);
						isqlGlob.prints(bad_dialect_buf);
					}
				}
			}
		}
	}
	else
	{
		// handle non numeric invalid "set sql dialect" case
		isqlGlob.SQL_dialect = old_SQL_dialect;	// restore SQL dialect
		bad_dialect = true;
		sprintf(bad_dialect_buf, "%s%s", "invalid SQL dialect ",
				dialect_str);
		ret = ps_ERR;
	}
	return ret;
}


static processing_state get_statement(Firebird::string& statement,
							const TEXT* statement_prompt)
{
/**************************************
 *
 *	g e t _ s t a t e m e n t
 *
 **************************************
 *
 * Functional description
 *	Get an SQL statement, or QUIT/EXIT command to process
 *
 *	Arguments:  Pointer to statement, size of statement_buffer and prompt msg.
 *
 **************************************/
	processing_state ret = CONT;

	// Lookup the continuation prompt once
	TEXT con_prompt[MSG_LENGTH];
	IUTILS_msg_get(CON_PROMPT, con_prompt);

	if (Interactive && !Input_file || setValues.Echo) {
		ISQL_prompt(statement_prompt);
	}

	// Clear out statement buffer
	statement.resize(0);

	// Set count of characters to zero

	size_t valuable_count = 0; // counter of valuable (non-space) chars
	size_t comment_pos = 0; // position of block comment start
	size_t non_comment_pos = 0; // position of char after block comment
	const size_t term_length = isqlGlob.Termlen - 1; // additional variable for decreasing calculation

	Filelist->Ifp().indev_line = Filelist->Ifp().indev_aux;
	bool done = false;

	enum
	{
		normal,
		in_single_line_comment,
		in_block_comment,
		in_single_quoted_string,
		in_double_quoted_string
	} state = normal;

	char lastChar = '\0';
	char altQuoteChar = '\0';
	unsigned altQuoteStringLength = 0;

	while (!done)
	{
		SSHORT c = getNextInputChar();
		switch (c)
		{
		case EOF:
			// Go back to getc if we get interrupted by a signal.

			if (SYSCALL_INTERRUPTED(errno))
			{
				errno = 0;
				break;
			}

			// If there was something valuable before EOF - error
			if (valuable_count > 0)
			{
				TEXT errbuf[MSG_LENGTH];
				IUTILS_msg_get(UNEXPECTED_EOF, errbuf);
				STDERROUT(errbuf);
				Exit_value = FINI_ERROR;
				ret = FAIL;
			}

			// If we hit EOF at the top of the flist, exit time

			if (Filelist->count() == 1)
				return FOUND_EOF;

			// If this is not tmpfile, close it

			if (!Filelist->sameInputAndOutput())
				Filelist->Ifp().close();

			// Reset to previous after other input

			Filelist->removeIntoIfp();

			if (Interactive && !Input_file || setValues.Echo)
				ISQL_prompt(statement_prompt);

			// CVC: Let's detect if we went back to the first level.
			if (Filelist->readingStdin())
			{
				Interactive = !stdin_redirected();
				Input_file = false;
			}

			// Try to convince the new routines to go back to previous file(s)
			// This should fix the INPUT bug introduced with editline.
			getColumn = -1;
			break;

		case '\n':
//		case '\0': // In particular with readline the \n is removed
			if (state == in_single_line_comment)
			{
				state = normal;
			}

			// Catch the help ? without a terminator
			if (statement.length() == 1 && statement[0] == '?')
			{
				c = 0;
				done = true;
				break;
			}

			// If in a comment, keep reading
			if (Interactive && !Input_file || setValues.Echo)
			{
				if (state == in_block_comment)
				{
					//  Block comment prompt.
					ISQL_prompt("--> ");
				}
				else if (valuable_count == 0)
				{
					// Ignore a series of nothing at the beginning
					ISQL_prompt(statement_prompt);
				}
				else
				{
					ISQL_prompt(con_prompt);
				}
			}

			break;

		case '-':
			// Could this the be start of a single-line comment.
			if (state == normal && statement.length() > 0 &&
				statement[statement.length() - 1] == '-')
			{
				state = in_single_line_comment;
				if (valuable_count == 1)
					valuable_count = 0;
			}
			break;

		case '*':
			// Could this the be start of a comment.  We can only look back,
			// not forward.
			// Ignore possibilities of a comment beginning inside
			// quoted strings.
			if (state == normal && statement.length() > 0 &&
				statement[statement.length() - 1] == '/' && statement.length() != non_comment_pos)
			{
				state = in_block_comment;
				comment_pos = statement.length() - 1;
				if (valuable_count == 1)
					valuable_count = 0;
			}
			break;

		case '/':
			// Perhaps this is the end of a comment.
			// Ignore possibilities of a comment ending inside
			// quoted strings.
			// Ignore things like /*/ since it isn't a block comment; only the start of it. Or end.
			if (state == in_block_comment && statement.length() > 2 &&
				statement[statement.length() - 1] == '*' && statement.length() > comment_pos + 2)
			{
				state = normal;
				non_comment_pos = statement.length() + 1; // mark start of non-comment to track this: /**/*
				valuable_count--; // This char is not valuable
			}
			break;

		case SINGLE_QUOTE:
			switch (state)
			{
				case normal:
					if (lastChar == 'q' || lastChar == 'Q')
					{
						statement += (lastChar = c);
						altQuoteChar = c = getNextInputChar();
						altQuoteStringLength = statement.length();

						switch (altQuoteChar)
						{
							case '{':
								altQuoteChar = '}';
								break;
							case '(':
								altQuoteChar = ')';
								break;
							case '[':
								altQuoteChar = ']';
								break;
							case '<':
								altQuoteChar = '>';
								break;
						}
					}
					else
						altQuoteChar = '\0';

					state = in_single_quoted_string;
					break;
				case in_single_quoted_string:
					if (!altQuoteChar || (statement.length() != altQuoteStringLength + 1 && lastChar == altQuoteChar))
						state = normal;
					break;
			}
			break;

		case DBL_QUOTE:
			switch (state)
			{
				case normal:
					state = in_double_quoted_string;
					break;
				case in_double_quoted_string:
					state = normal;
					break;
			}
			break;


		default:
			if (state == normal && c == isqlGlob.global_Term[term_length] &&
				// one-char terminator or the beginning also match
				(isqlGlob.Termlen == 1u ||
				 (valuable_count >= term_length &&
				  strncmp(&statement[statement.length() - term_length],
				  	isqlGlob.global_Term, term_length) == 0)))
			{
				c = 0;
				done = true;
				statement.resize(statement.length() - term_length);
			}
		}

		// Any non-space character is significant if not in comment
		if (state != in_block_comment &&
			state != in_single_line_comment &&
			!fb_isspace(c) && c != EOF)
		{
			valuable_count++;
			if (valuable_count == 1) // this is the first valuable char in stream
			{
				// ignore all previous crap
				statement.resize(0);
				non_comment_pos = 0;
			}
		}

		statement += (lastChar = c);
	}

	// If this was a null statement, skip it
	if (ret == CONT && statement.isEmpty())
		ret = SKIP;

	if (ret == CONT)
		ret = frontend(statement.c_str());

	if (ret == CONT)
	{
		// Place each non frontend statement in the temp file if we are reading
		// from stdin.

		Filelist->saveCommand(statement.c_str(), isqlGlob.global_Term);
	}

	return ret;
}


void ISQL_get_version(bool call_by_create_db)
{
/**************************************
 *
 *	I S Q L _ g e t _ v e r s i o n
 *
 **************************************
 *
 * Functional description
 *	finds out if the database we just attached to is
 *	V4 or newer as well as other info.
 *
 **************************************/
	const UCHAR db_version_info[] =
	{
		isc_info_ods_version,
		isc_info_ods_minor_version,
		isc_info_db_sql_dialect,
		frb_info_att_charset,
		Version_info ? isc_info_firebird_version: isc_info_end,
		isc_info_end
	};
	/*
	   ** Each info item requested will return
	   **
	   **	 1 byte for the info item tag
	   **	 2 bytes for the length of the information that follows
	   **	 1 to 4 bytes of integer information
	   **
	   ** isc_info_end will not have a 2-byte length - which gives us
	   ** some padding in the buffer.
	 */

	// UCHAR buffer[sizeof(db_version_info) * (1 + 2 + 4)];

	// Now we are also getting the Firebird server version which is a
	// string the above calculation does not apply.	 NM 03-Oct-2001

	UCHAR buffer[PRINT_BUFFER_LENGTH];
	char bad_dialect_buf[BUFFER_LENGTH512];
	bool print_warning = false;

	global_dialect_spoken = 0;

	if (!DB)
		return;
	DB->getInfo(fbStatus, sizeof(db_version_info), db_version_info,
						  sizeof(buffer), buffer);
	if(ISQL_errmsg(fbStatus))
	{
		return;
	}

	for (Firebird::ClumpletReader p(Firebird::ClumpletReader::InfoResponse, buffer, sizeof(buffer)); !p.isEof(); p.moveNext())
	{
		UCHAR item = p.getClumpTag();
		if (item == isc_info_end)
			break;

		switch (item)
		{
		case isc_info_ods_version:
			isqlGlob.major_ods = p.getInt();
			break;
		case isc_info_ods_minor_version:
			isqlGlob.minor_ods = p.getInt();
			break;
		case isc_info_db_sql_dialect:
			global_dialect_spoken = p.getInt();
			if (isqlGlob.major_ods < ODS_VERSION10)
			{
				if (isqlGlob.SQL_dialect > SQL_DIALECT_V5 && setValues.Warnings)
				{
					isqlGlob.printf(NEWLINE);
					sprintf(bad_dialect_buf, "%s%s%s%d%s%s",
							"WARNING: Pre IB V6 database only speaks",
							" SQL dialect 1 and ",
							"does not accept Client SQL dialect ",
							isqlGlob.SQL_dialect,
							" . Client SQL dialect is reset to 1.", NEWLINE);
					isqlGlob.prints(bad_dialect_buf);
				}
			}
			else
			{
				// ODS 10 databases

				switch (global_dialect_spoken)
				{
				case SQL_DIALECT_V5:
					if (isqlGlob.SQL_dialect > SQL_DIALECT_V5)
						print_warning = true;
					break;

				case SQL_DIALECT_V6:
					if (isqlGlob.SQL_dialect != 0 && isqlGlob.SQL_dialect < SQL_DIALECT_V6)
						print_warning = true;
					break;
				default:
					break;
				}
				if (print_warning && setValues.Warnings)
				{
					print_warning = false;
					isqlGlob.printf(NEWLINE);
					sprintf(bad_dialect_buf, "%s%d%s%d%s%s",
							"WARNING: This database speaks SQL dialect ",
							global_dialect_spoken,
							" but Client SQL dialect was set to ",
							isqlGlob.SQL_dialect, " .", NEWLINE);
					isqlGlob.prints(bad_dialect_buf);
				}
			}
			break;
		case isc_info_error:
			// Error indicates an option was not understood by the
			// remote server.
			if (p.getBytes()[0] == isc_info_firebird_version)
			{
				// must be an old or non Firebird server
				break;
			}
			if (isqlGlob.SQL_dialect && isqlGlob.SQL_dialect != SQL_DIALECT_V5 && setValues.Warnings)
			{
				isqlGlob.printf(NEWLINE);
				if (call_by_create_db)
					sprintf(bad_dialect_buf, "%s%s%d%s%s",
							"WARNING: Pre IB V6 server only speaks SQL dialect 1",
							" and does not accept Client SQL dialect ",
							isqlGlob.SQL_dialect,
							" . Client SQL dialect is reset to 1.", NEWLINE);
				else
				{
					//connecting_to_pre_v6_server = true; Not used anywhere.
					sprintf(bad_dialect_buf, "%s%s%d%s%s",
							"ERROR: Pre IB V6 server only speaks SQL dialect 1",
							" and does not accept Client SQL dialect ",
							isqlGlob.SQL_dialect,
							" . Client SQL dialect is reset to 1.", NEWLINE);
				}
				isqlGlob.prints(bad_dialect_buf);
			}
			else
			{
				if (isqlGlob.SQL_dialect == 0)
				{
					//connecting_to_pre_v6_server = true; Not used anywhere.
					sprintf(bad_dialect_buf, "%s%s%d%s%s",
							"ERROR: Pre IB V6 server only speaks SQL dialect 1",
							" and does not accept Client SQL dialect ",
							isqlGlob.SQL_dialect,
							" . Client SQL dialect is reset to 1.", NEWLINE);
					isqlGlob.prints(bad_dialect_buf);
				}
			}
			break;
		case isc_info_firebird_version:
			if (Version_info)
			{
				// This information will be skipped if the server isn't given enough buffer
				// to put it all. It's a FULL or NOTHING answer. It grows with redirection.
				// The command SHOW version that calls isc_version() will return more info.
				isqlGlob.printf("Server version:%s", NEWLINE);
				const UCHAR* q = p.getBytes(); // We don't want to spoil p with a wrong calculation.
				const UCHAR* limit = q + p.getClumpLength();
				for (int times = *q++; times && q < limit; --times)
				{
					int l = *q++;
					if (l > limit - q)
						l = limit - q;

					isqlGlob.printf("%.*s%s", l, q, NEWLINE);
					q += l;
				}
			}
			break;

		case frb_info_att_charset:
			isqlGlob.att_charset = p.getInt();
			break;

		default:
			isqlGlob.printf("Internal error: Unexpected isc_info_value %d%s",
							item, NEWLINE);
			break;
		}
	}

	if (isqlGlob.major_ods < ODS_VERSION8)
	{
		TEXT errbuf[MSG_LENGTH];
		IUTILS_msg_get(SERVER_TOO_OLD, errbuf);
		STDERROUT(errbuf);
		return;
	}

	// If the remote server did not respond to our request for
	// "dialects spoken", then we can assume it can only speak
	// the V5 dialect.  We automatically change the connection
	// dialect to that spoken by the server.  Otherwise the
	// current dialect is set to whatever the user requested.

	if (global_dialect_spoken == 0)
		isqlGlob.SQL_dialect = SQL_DIALECT_V5;
	else if (isqlGlob.major_ods < ODS_VERSION10)
		isqlGlob.SQL_dialect = global_dialect_spoken;
	else if (isqlGlob.SQL_dialect == 0)	// client SQL dialect has not been set
		isqlGlob.SQL_dialect = global_dialect_spoken;

	if (global_dialect_spoken > 0)
		isqlGlob.db_SQL_dialect = global_dialect_spoken;
	else
		isqlGlob.db_SQL_dialect = SQL_DIALECT_V5;
}


void ISQL_ri_action_print(const TEXT* ri_action_str,
						  const TEXT* ri_action_prefix_str,
						  bool all_caps)
{
/**************************************
 *
 *	I S Q L _ r i _ a c t i o n _ p r i n t
 *
 **************************************
 *
 * Functional description
 *	prints the description of ref. integrity actions.
 *	  The actions must be one of the cascading actions or RESTRICT.
 *	  RESTRICT is used to indicate that the user did not specify any
 *	  actions, so do not print it out.
 *
 **************************************/
	for (const ri_actions* ref_int = ri_actions_all; ref_int->ri_action_name;
		++ref_int)
	{
		if (!strcmp(ref_int->ri_action_name, ri_action_str))
		{
			if (*ref_int->ri_action_print_caps)
			{
				// we have something to print
				if (all_caps)
					isqlGlob.printf("%s %s", ri_action_prefix_str, ref_int->ri_action_print_caps);
				else if (*ref_int->ri_action_print_mixed)
					isqlGlob.printf("%s %s", ri_action_prefix_str, ref_int->ri_action_print_mixed);
			}
			return;
		}
	}

	fb_assert(FALSE);
}


static bool get_numeric(const UCHAR* str2,
						USHORT length,
						SSHORT* scale,
						SINT64* ptr)
{
/**************************************
 *
 *	  g e t _ n u m e r i c
 *
 **************************************
 *
 * Functional description
 *	  Convert a numeric literal (str2) to its binary value.
 *
 *	  The binary value (int64) is stored at the
 *	  address given by ptr.
 *
 **************************************/
	SINT64 value = 0;

	SSHORT local_scale = 0, sign = 0;
	bool digit_seen = false, fraction = false;

	const UCHAR* const end = str2 + length;
	for (const UCHAR* p = str2; p < end; p++)
	{
		if (DIGIT(*p))
		{
			digit_seen = true;

			// Before computing the next value, make sure there will be
			// no overflow. Trying to detect overflow after the fact is
			// tricky: the value doesn't always become negative after an
			// overflow!

			if (value > INT64_LIMIT)
				return false;

			if (value == INT64_LIMIT)
			{
				// possibility of an overflow

				if ((*p > '8' && sign == -1) || (*p > '7' && sign != -1))
					return false;
			}

			// Force the subtraction to be performed before the addition,
			// thus preventing a possible signed arithmetic overflow.
			value = value * 10 + (*p - '0');
			if (fraction)
				--local_scale;
		}
		else if (*p == '.')
		{
			if (fraction)
				return false;

			fraction = true;
		}
		else if (*p == '-' && !digit_seen && !sign && !fraction)
			sign = -1;
		else if (*p == '+' && !digit_seen && !sign && !fraction)
			sign = 1;
		else if (*p != BLANK)
			return false;
	}

	if (!digit_seen)
		return false;

	*scale = local_scale;
	*(SINT64*) ptr = ((sign == -1) ? -value : value);
	return true;
}


// Helper to print boolean values in the SET options.
static void print_set(const char* str, bool v)
{
	isqlGlob.printf("%-25s%s%s", str, v ? "ON" : "OFF", NEWLINE);
}


static processing_state print_sets()
{
/**************************************
 *
 *	p r i n t _ s e t s
 *
 **************************************
 *
 * Functional description
 *	Print the current set values
 *
 **************************************/

	print_set("Print statistics:", setValues.Stats);
	print_set("Echo commands:", setValues.Echo);
	print_set("List format:", setValues.List);
	print_set("Show Row Count:", setValues.Docount);
	//print_set("Row Count:", setValues.Docount);   // Changed print to the above to avoid confusion with next one
	isqlGlob.printf("%-25s%lu%s", "Select maxrows limit:", setValues.maxRows, NEWLINE);
	print_set("Autocommit DDL:", setValues.Autocommit);
	print_set("Access Plan:", setValues.Plan);
	print_set("Access Plan only:", setValues.Planonly);
	print_set("Explain Access Plan:", setValues.ExplainPlan);

	isqlGlob.printf("%-25s", "Display BLOB type:");
	switch (setValues.Doblob)
	{
	case ALL_BLOBS:
		isqlGlob.printf("ALL");
		break;
	case NO_BLOBS:
		isqlGlob.printf("NONE");
		break;
	default:
		isqlGlob.printf("%d", setValues.Doblob);
	}
	isqlGlob.printf(NEWLINE);

	if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)) {
		isqlGlob.printf("%-25s%s%s", "Set names:", setValues.ISQL_charset, NEWLINE);
	}

	print_set("Column headings:", setValues.Heading);

	if (setValues.global_Cols.count())
	{
		isqlGlob.printf("Column print widths:%s", NEWLINE);
		const ColList::item* p = setValues.global_Cols.getHead();
		while (p)
		{
			isqlGlob.printf("%s%s width: %d%s", TAB_AS_SPACES, p->col_name, p->col_len, NEWLINE);
			p = p->next;
		}
	}
	isqlGlob.printf("%-25s%s%s", "Terminator:", isqlGlob.global_Term, NEWLINE);

	print_set("Time:", setValues.Time_display);
	print_set("Warnings:", setValues.Warnings);
	print_set("Bail on error:", setValues.BailOnError);
	print_set("Keep transaction params:", setValues.KeepTranParams);
	if (setValues.KeepTranParams)
		isqlGlob.printf("    %s%s", TranParams->c_str(), NEWLINE);
	return SKIP;
}


static processing_state help(const TEXT* what)
{
/**************************************
 *
 *	h e l p
 *
 **************************************
 *
 * Functional description
 *	List the known commands.
 *
 **************************************/

	// Ordered list of help messages to display.  Use -1 to terminate list,
	// and 0 for an empty blank line
	static const SSHORT help_ids[] =
	{
		HLP_FRONTEND,			// Frontend commands:
		HLP_BLOBDMP,			// BLOBDUMP <blobid> <file>	-- dump BLOB to a file
		HLP_BLOBVIEW,			// BLOBVIEW <blobid>		-- view BLOB in text editor
		HLP_EDIT,				// EDIT	 [<filename>]		-- edit SQL script file and execute
		HLP_EDIT2,				// EDIT						-- edit current command buffer and execute
		HLP_HELP,				// HELP						-- display this menu
		HLP_INPUT,				// INput	<filename>		-- take input from the named SQL file
		HLP_OUTPUT,				// OUTput   [<filename>]	-- write output to named file
		HLP_OUTPUT2,			// OUTput					-- return output to stdout
		HLP_SET_ROOT,			// SET	  <option>			-- (use HELP SET for list)
		HLP_SHELL,				// SHELL	<command>		-- execute Operating System command in sub-shell
		HLP_SHOW,				// SHOW	 <object> [<name>]	-- display system information
		HLP_OBJTYPE,			//	<object> = CHECK, DATABASE, DOMAIN, EXCEPTION, FILTER, FUNCTION, GENERATOR,
		HLP_OBJTYPE2,			//			   GRANT, INDEX, PROCEDURE, ROLE, SQL DIALECT, SYSTEM, TABLE,
		HLP_OBJTYPE3,			//			   TRIGGER, VERSION, VIEW
		HLP_EXIT,				// EXIT					   -- exit and commit changes
		HLP_QUIT,				// QUIT					   -- exit and roll back changes
		0,
		HLP_ALL,				// All commands may be abbreviated to letters in CAPitals
		-1						// end of list
	};

	static const SSHORT help_set_ids[] =
	{
		HLP_SETCOM,				//Set commands:
		HLP_SET,				//	SET						-- display current SET options
		HLP_SETAUTO,			//	SET AUTOddl				-- toggle autocommit of DDL statements
		HLP_SETBAIL,			//	SET BAIL				-- toggle bailing out on errors in non-interactive mode
		HLP_SETBLOB,			//	SET BLOB [ALL|<n>]		-- display BLOBS of subtype <n> or ALL
		HLP_SETBLOB2,			//	SET BLOB				-- turn off BLOB display
		HLP_SETCOUNT,			//	SET COUNT				-- toggle count of selected rows on/off
		HLP_SETMAXROWS,			//	SET MAXROWS [<n>]		-- toggle limit of selected rows to <n>, zero is no limit
		HLP_SETECHO,			//	SET ECHO				-- toggle command echo on/off
		HLP_SETEXPLAIN,			//	SET EXPLAIN				-- toggle display of query plan in the explained form
		HLP_SETHEADING,			//  SET HEADING 	        -- toggle column titles display on/off
		HLP_SETLIST,			//	SET LIST				-- toggle column or table display format
		HLP_SETNAMES,			//	SET NAMES <csname>		-- set name of runtime character set
		HLP_SETPLAN,			//	SET PLAN				-- toggle display of query access plan
		HLP_SETPLANONLY,		//	SET PLANONLY			-- toggle display of query plan without executing
		HLP_SETSQLDIALECT,		//	SET SQL DIALECT <n>		-- set sql dialect to <n>
		HLP_SETSTAT,			//	SET STATs				-- toggle display of performance statistics
		HLP_SETTIME,			//	SET TIME				-- toggle display of timestamp with DATE values
		HLP_SETTERM,			//	SET TERM <string>		-- change statement terminator string
		HLP_SETWIDTH,			//	SET WIDTH <col> [<n>]	-- set/unset print width to <n> for column <col>
		0,
		HLP_ALL,				// All commands may be abbreviated to letters in CAPitals
		-1						// end of list
	};

	TEXT msg[MSG_LENGTH];
	const SSHORT* msgid;
	if (!strcmp(what, "SET")) {
		msgid = help_set_ids;
	}
	else {
		msgid = help_ids;
	}
	for (; *msgid != -1; msgid++)
	{
		if (*msgid != 0)
		{
			IUTILS_msg_get(*msgid, msg);
			IUTILS_printf(Help, msg);
		}
		IUTILS_printf(Help, NEWLINE);
	}
	return (SKIP);
}


static bool isyesno(const TEXT* buffer)
{
/**********************************************
 *
 *	i s y e s n o
 *
 **********************************************
 *
 * Functional description
 *	check if the first letter of the user's response
 *	corresponds to the first letter of Yes
 *	(in whatever language they are using)
 *
 *	returns true for Yes, otherwise false.
 *
 **********************************************/

	if (!have_trans)
	{
		// get the translation if we don't have it already
		IUTILS_msg_get(YES_ANS, sizeof(yesword), yesword);
		have_trans = true;
	}

	// Just check first byte of yes response -- could be multibyte problem

	return UPPER7(buffer[0]) == UPPER7(yesword[0]);
}


static bool printUser(const char* dbName)
{
	if (!frontendTransaction())
		return false;

	class FbTransCommit
	{
	public:
		~FbTransCommit()
		{
			if (DB && fbTrans)
			{
				fbTrans->rollback(fbStatus);
				if (succeeded())
					fbTrans = NULL;
			}
		}
	};
	FbTransCommit fbTransCommit;

	const char* sql = "SELECT CURRENT_USER, CURRENT_ROLE FROM RDB$DATABASE";

	Firebird::RefPtr<Firebird::IStatement> st(Firebird::REF_NO_INCR,
		DB->prepare(fbStatus, fbTrans, 0, sql, 3, Firebird::IStatement::PREPARE_PREFETCH_METADATA));
	if (failed())
		return false;
	Firebird::RefPtr<Firebird::IMessageMetadata> m(Firebird::REF_NO_INCR,
		st->getOutputMetadata(fbStatus));
	if (failed())
		return false;
	unsigned bs = m->getMessageLength(fbStatus);
	if (failed())
		return false;
	Firebird::UCharBuffer outBuf;
	UCHAR* buf = outBuf.getBuffer(bs);
	st->execute(fbStatus, fbTrans, NULL, NULL, m, buf);
	if (failed())
		return false;

	struct FieldInfo
	{
		const char* text;
		const char* skip;
	};
	FieldInfo fieldInfo[2] = {
		{"User", NULL},
		{"Role", "NONE"}
	};

	bool wasOut = dbName && dbName[0];
	if (wasOut)
		isqlGlob.printf("Database: %s", dbName);

	for (unsigned i = 0; i < FB_NELEM(fieldInfo); ++i)
	{
		IsqlVar v;
		if (ISQL_fill_var(&v, m, i, buf) == ps_ERR)
			return false;
		if (*v.nullInd)
			continue;

		Firebird::string txt;
		switch(v.type & ~1)
		{
		case SQL_TEXT:
			txt.assign(v.value.asChar, v.length);
			break;
		case SQL_VARYING:
			txt.assign(v.value.asVary->vary_string, v.value.asVary->vary_length);
			break;
		}

		txt.trim();

		if (fieldInfo[i].skip && txt == fieldInfo[i].skip)
			continue;

		isqlGlob.printf("%s%s: %s", wasOut ? ", " : "", fieldInfo[i].text, txt.c_str());
		wasOut = true;
	}

	if (wasOut)
		isqlGlob.printf("%s", NEWLINE);

	return true;
}


static processing_state newdb(TEXT* dbname,
					const TEXT* usr,
					const TEXT* psw,
					int numbufs,
					const TEXT* sql_role_nm,
					bool start_user_trans)
{
/**************************************
 *
 *	n e w d b
 *
 **************************************
 *
 * Functional description
 *	Change the current database from what it was.
 *	This procedure is called when we first enter this program.
 *
 *	Parameters:	dbname	  -- Name of database to open
 *			usr		 -- user name, if given
 *			psw		 -- password, if given
 *			numbufs	 -- # of connection cache buffers, if given, 0 if not
 *			sql_role_nm -- sql role name
 *
 **************************************/
	// No name of a database, just return an error

	if (!dbname || !*dbname)
		return ps_ERR;

	// Since the dbname is set already, in the case where a database is specified
	// on the command line, we need to save it so ISQL_disconnect does not NULL it
	// out.  We will restore it after the disconnect.  The save_database buffer
	// will also be used to translate dbname to the proper character set.

	const SLONG len = static_cast<SLONG>(chop_at(dbname, MAXPATHLEN));
	SCHAR* save_database = (SCHAR*) ISQL_ALLOC(len + 1);
	if (!save_database)
		return ps_ERR;

	strcpy(save_database, dbname);
	ISQL_disconnect_database(false);
	strcpy(dbname, save_database);
	ISQL_FREE(save_database);

	TEXT local_psw[BUFFER_LENGTH128];
	TEXT local_usr[BUFFER_LENGTH128];
	TEXT local_sql_role[BUFFER_LENGTH256];

	// global user and passwords are set only if they are not originally set

	local_psw[0] = 0;
	local_usr[0] = 0;
	local_sql_role[0] = 0;

	// Strip quotes if well-intentioned

	strip_quotes(dbname, isqlGlob.global_Db_name);
	if (usr)
		strcpy(local_usr, usr);
	strip_quotes(psw, local_psw);

	// if local user is not specified, see if global options are
	// specified - don't let a global role setting carry forward if a
	// specific local user was specified

	if (sql_role_nm)
		strcpy(local_sql_role, sql_role_nm);

	if (!(strlen(local_sql_role)) && global_role)
		strcpy(local_sql_role, isqlGlob.Role);

	if (!(strlen(local_usr)) && global_usr)
		strcpy(local_usr, isqlGlob.User);

	if (!(strlen(local_psw)) && global_psw)
		strcpy(local_psw, Password);

	int local_numbufs = numbufs;
	if ((local_numbufs == 0) && has_global_numbufs)
		local_numbufs = global_numbufs;

	// Build up a dpb
	Firebird::ClumpletWriter dpb(Firebird::ClumpletReader::dpbList, MAX_DPB_SIZE);

	if (*setValues.ISQL_charset && strcmp(setValues.ISQL_charset, DEFCHARSET)) {
		dpb.insertString(isc_dpb_lc_ctype, setValues.ISQL_charset, fb_strlen(setValues.ISQL_charset));
	}

    FB_SIZE_T l;
	if (l = fb_strlen(local_usr)) {
		dpb.insertString(isc_dpb_user_name, local_usr, l);
	}

	if (l = fb_strlen(local_psw)) {
		dpb.insertString(isc_dpb_password, local_psw, l);
	}

	if (l = fb_strlen(local_sql_role))
	{
		dpb.insertInt(isc_dpb_sql_dialect, isqlGlob.SQL_dialect);
		dpb.insertString(isc_dpb_sql_role_name, local_sql_role, l);
	}

	if (local_numbufs > 0) {
		dpb.insertInt(isc_dpb_num_buffers, local_numbufs);
	}

	if (Nodbtriggers)
		dpb.insertInt(isc_dpb_no_db_triggers, 1);

#ifdef TRUSTED_AUTH
	if (Trusted_auth) {
		dpb.insertTag(isc_dpb_trusted_auth);
	}
#endif

	{ // scope
		const TEXT* local_name = isqlGlob.global_Db_name;

		DB = fbProvider->attachDatabase(fbStatus, local_name, dpb.getBufferLength(), dpb.getBuffer());
		if (ISQL_errmsg(fbStatus))
		{
			isqlGlob.global_Db_name[0] = '\0';
			return FAIL;
		}

		// Make it read owner name to display grantor correctly
		SHOW_read_owner();

		// No use in cancel when running non-end-user operators
		DB->cancelOperation(fbStatus, fb_cancel_disable);
	} // scope

	ISQL_get_version(false);

	if (*local_sql_role)
	{
		switch (isqlGlob.SQL_dialect)
		{
		case SQL_DIALECT_V5:
			// Uppercase the Sql isqlGlob.Role name
			IUTILS_make_upper(local_sql_role);
			break;
		case SQL_DIALECT_V6_TRANSITION:
		case SQL_DIALECT_V6:
			if (*local_sql_role == DBL_QUOTE || *local_sql_role == SINGLE_QUOTE)
			{
				// Remove the delimited quotes and escape quote from ROLE name.
				const TEXT end_quote = *local_sql_role;
				IUTILS_remove_and_unescape_quotes(local_sql_role, end_quote);
			}
			else {
				IUTILS_make_upper(local_sql_role);
			}
			break;
		default:
			break;
		}
	}

	// CVC: We do not require those pesky transactions for -x or -a options.
	// Metadata extraction works exclusively with default transaction gds__trans.

	if (start_user_trans)
	{
		// Start the user transaction and default transaction

		if (!M__trans)
		{
			M_Transaction();
			if (D__trans)
				commit_trans(&D__trans);
			if (setValues.Autocommit)
				D_Transaction();
		}
	}

	// CVC: Do not put the user and pw used to extract metadata in a script!
	// Only acknowledge user connection parameters in interactive logins.

	if (Interactive && !printUser(dbname))
	{
		if (local_usr[0] != '\0')
		{
			if (local_sql_role[0] != '\0')
			{
				isqlGlob.printf("Database:  %s, User: %s, Role: %s%s",
						dbname, local_usr, local_sql_role, NEWLINE);
			}
			else {
				isqlGlob.printf("Database:  %s, User: %s%s", dbname, local_usr, NEWLINE);
			}
		}
		else
		{
			if (local_sql_role[0] != '\0') {
				isqlGlob.printf("Database:  %s, Role:  %s%s", dbname, local_sql_role, NEWLINE);
			}
			else {
				isqlGlob.printf("Database:  %s%s", dbname, NEWLINE);
			}
		}
	}

	global_Stmt = NULL;

	return SKIP;
}


static processing_state newinput(const TEXT* infile)
{
/**************************************
 *
 *	n e w i n p u t
 *
 **************************************
 *
 * Functional description
 *	Read commands from the named input file
 *
 *	Parameters:  infile -- Second word of the command line
 *		filelist is a stack of file pointers to
 *			return from nested inputs
 *
 *	The result of calling this is to point the
 *	global input file pointer, Ifp, to the named file.
 *
 **************************************/
	TEXT errbuf[MSG_LENGTH];

	// If there is no file name specified, return error
	if (!infile || !*infile) {
		return ps_ERR;
	}

	TEXT path[MAXPATHLEN];
	strip_quotes(infile, path);

	PathName file;

	if (PathUtils::isRelative(path))
	{
		PathName newPath, temp;
		PathUtils::splitLastComponent(newPath, temp, Filelist->Ifp().fileName(false));

		PathUtils::concatPath(file, newPath, path);
	}
	else
		file = path;

	// filelist is a linked list of file pointers.  We must add a node to
	// the linked list before discarding the current Ifp.
	// Filelist is a global pointing to base of list.

	FILE* fp = fopen(file.c_str(), "r");
	if (fp)
	{
		Filelist->insertIfp();
		Filelist->Ifp().init(fp, file.c_str(), path);
	}
	else
	{
		IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << path);
		STDERROUT(errbuf);
		return FAIL;
	}

	Input_file = true;
	return SKIP;
}


static processing_state newoutput(const TEXT* outfile)
{
/**************************************
 *
 *	n e w o u t p u t
 *
 **************************************
 *
 * Functional description
 *	Change the current output file
 *
 *	Parameters:  outfile : Name of file to receive query output
 *
 **************************************/
	processing_state ret = SKIP;
	// If there is a file name, attempt to open it for append

	if (*outfile)
	{
		TEXT path[MAXPATHLEN];
		strip_quotes(outfile, path);
		outfile = path;
		FILE* fp = fopen(outfile, "a");
		if (fp)
		{
			if (isqlGlob.Out && isqlGlob.Out != stdout)
				fclose(isqlGlob.Out);
			isqlGlob.Out = fp;
			if (Merge_stderr)
				isqlGlob.Errfp = isqlGlob.Out;
			if (Merge_diagnostic)
			    Diag = isqlGlob.Out;
		}
		else
		{
			TEXT errbuf[MSG_LENGTH];
			IUTILS_msg_get(FILE_OPEN_ERR, errbuf, SafeArg() << outfile);
			STDERROUT(errbuf);
			ret = FAIL;
		}
	}
	else
	{
		// Revert to stdout
		if (isqlGlob.Out != stdout)
		{
			fclose(isqlGlob.Out);
			isqlGlob.Out = stdout;
			if (Merge_stderr)
				isqlGlob.Errfp = isqlGlob.Out;
			if (Merge_diagnostic)
			    Diag = isqlGlob.Out;
		}
	}
	return (ret);
}


static processing_state newsize(const TEXT* colname, const TEXT* sizestr)
{
/**************************************
 *
 *	n e w s i z e
 *
 **************************************
 *
 * Functional description
 *	Add a column name and print width to collist
 *
 **************************************/
	if (!*colname || (strlen(colname) >= QUOTEDLENGTH))
		return ps_ERR;

	char buf[QUOTEDLENGTH];
	if (colname[0] == DBL_QUOTE)
	{
		strcpy(buf, colname);
		IUTILS_remove_and_unescape_quotes(buf, DBL_QUOTE);
		colname = buf;
	}

	if (strlen(colname) > MAX_SQL_IDENTIFIER_LEN)
		return ps_ERR;

	// If no size is given, remove the entry
	if (!*sizestr)
	{
		// We don't signal error if the item to be removed doesn't exist.
		setValues.global_Cols.remove(colname);
		return SKIP;
	}

	const int size = atoi(sizestr);
	if (size <= 0)
		return ps_ERR;

	// We don't care if it was insertion or replacement.
	setValues.global_Cols.put(colname, size);
	return SKIP;
}

static processing_state newMaxRows(const TEXT* newMaxRowsStr)
{
/**************************************
 *
 *	newMaxRows
 *
 **************************************
 *
 * Functional description
 * Sets the new value for the setValues.maxRows limit (max rows to be retrieved).
 *
 **************************************/

	char* p;

	errno = 0;
	const long newMaxRows = strtol(newMaxRowsStr, &p, 10);
	// I was going to use this one, but "-1" parses as max ulong without error
	// and it would be politer to give an error.
	//const ULONG newMaxRows = strtoul(newMaxRowsStr, &p, 10);

	// CVC: I added this block because Windows wasn't working according to Mark's
	// expectation: it only produces ERANGE. Thus, garbage like
	// set maxrows abs;  wasn't caught.
	if (p == newMaxRowsStr)
	{
		IUTILS_put_errmsg(MAXROWS_INVALID, SafeArg() << newMaxRowsStr);
		return ps_ERR;
	}

	switch (errno)
	{
	case 0: // everything correct
		break;
	case ERANGE:
		// Only ERANGE is part of the ANSI standard here.
		IUTILS_put_errmsg(MAXROWS_OUTOF_RANGE, SafeArg() << newMaxRowsStr << SLONG_MAX);
		return ps_ERR;
	default:
		// EINVAL and the like
		IUTILS_put_errmsg(MAXROWS_INVALID, SafeArg() << newMaxRowsStr);
		return ps_ERR;
	}

	if (newMaxRows < 0)
	{
		IUTILS_put_errmsg(MAXROWS_NEGATIVE, SafeArg() << newMaxRowsStr);
		return ps_ERR;
	}

	setValues.maxRows = newMaxRows;
	return SKIP;
}


static processing_state newtrans(const TEXT* statement)
{
/**************************************
 *
 *	n e w t r a n s
 *
 **************************************
 *
 * Functional description
 *	Intercept and handle a set transaction statement by zeroing M__trans
 *
 *	Leave the default transaction, fbTrans, alone
 *	Parameters:  The statement in its entirety
 *
 **************************************/

	if (!ISQL_dbcheck())
		return FAIL;

	if (end_trans() == FAIL)
		return FAIL;

	M__trans = 0;

	// M__trans = 0 after the commit or rollback. Ready for a new transaction

	M__trans = DB->execute(fbStatus, NULL, 0, statement, isqlGlob.SQL_dialect,
		NULL, NULL, NULL, NULL);
	if (ISQL_errmsg(fbStatus))
	{
		return FAIL;
	}

	if (setValues.KeepTranParams)
		TranParams->assign(statement);

	return SKIP;
}


static processing_state parse_arg(int argc, SCHAR** argv, SCHAR* tabname)
//	, FILE** sess) Last param was for wisql
{
/**************************************
 *
 *	p a r s e _ a r g
 *
 **************************************
 *
 * Functional description
 *	Parse command line flags
 *	All flags except database are -X.  Look for
 *	the - and make it so.
 *
 **************************************/
	processing_state ret = SKIP;

	TEXT errbuf[MSG_LENGTH];

	// Initialize database name

	isqlGlob.global_Db_name[0] = '\0';
	isqlGlob.global_Target_db[0] = '\0';
	Password[0] = '\0';
	isqlGlob.User[0] = '\0';
	isqlGlob.Role[0] = '\0';
	global_numbufs = 0;
	Quiet = false;
	Exit_value = FINI_OK;

	// default behavior in V6.0 is SQL_DIALECT_V6

	requested_SQL_dialect = SQL_DIALECT_V6;

	Merge_stderr = false;
	Merge_diagnostic =  false;

	{ // scope
		const size_t lgth = strlen(DEFCHARSET);
		if (lgth < MAXCHARSET_SIZE)
			strcpy(setValues.ISQL_charset, DEFCHARSET);
		else
			fb_utils::copy_terminate(setValues.ISQL_charset, DEFCHARSET, MAXCHARSET_SIZE);
	} // scope

	// redirected stdin means act like -i was set

	Filelist->Ifp().init(stdin, "stdin", "stdin");

	// Terminators are initialized

	isqlGlob.Termlen = strlen(DEFTERM);
	if (isqlGlob.Termlen < MAXTERM_SIZE)
		strcpy(isqlGlob.global_Term, DEFTERM);
	else
	{
		isqlGlob.Termlen = MAXTERM_SIZE - 1;
		fb_utils::copy_terminate(isqlGlob.global_Term, DEFTERM, isqlGlob.Termlen + 1);
	}

	// Initialize list of input file pointers

	//Filelist->clear();

	// Interpret each command line argument

	const SCHAR switchchar = '-';

#ifdef	DEV_BUILD
	bool istable = false;
#endif

	Switches switches(isql_in_sw_table, FB_NELEM(isql_in_sw_table), true, true);

	for (int i = 1; i < argc; ++i)
	{
		const char* s = argv[i];
		// Look at flags to find unique match.  If the flag has an arg,
		// advance the pointer (and i).  Only change the return value
		// for extract switch or error.
		if (*s == switchchar)
		{
			int swid = IN_SW_ISQL_0;
			int swarg_int = 0;
			char* swarg_str = NULL;

			Firebird::string s2(s);
			Switches::in_sw_tab_t* option = switches.findSwitchMod(s2);
			if (option)
			{
				swid = option->in_sw;
				if (option->in_sw_state)
				{
					IUTILS_msg_get(USAGE_DUPSW, errbuf, SafeArg() << s);
					STDERROUT(errbuf);
					ret = ps_ERR;
					break;
				}
				//option->in_sw_state = true; It's not enough with switches that have multiple spellings
				switches.activate(swid);

				switch (option->in_sw_optype)
				{
				case iqoArgNone: // nothing to do
					break;
				case iqoArgString:
				case iqoArgInteger:
					// make sure swarg_str is really a pointer to argv, not something else !!
					if (++i < argc)
						swarg_str = argv[i];
					if (!swarg_str || !*swarg_str)
					{
						IUTILS_msg_get(USAGE_NOARG, errbuf, SafeArg() << s);
						STDERROUT(errbuf);
						ret = ps_ERR;
					}
					else if (option->in_sw_optype == iqoArgInteger)
					{
						char* err;
						long value = strtol(swarg_str, &err, 10);
						if (*err)
						{
							// conversion error
							IUTILS_msg_get(USAGE_NOTINT, errbuf, SafeArg() << swarg_str << s);
							STDERROUT(errbuf);
							ret = ps_ERR;
						}
						else if ((value < INT_MIN) || (value > INT_MAX))
						{
							IUTILS_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << s);
							STDERROUT(errbuf);
							ret = ps_ERR;
						}
						else {
							swarg_int = (int) value;
						}
					}
					break;
				default:
					fb_assert(false);
				}
			}

			// Quit the loop of interpreting if we got an error
			if (ret == ps_ERR)
				break;

			switch (swid)
			{
#ifdef DEV_BUILD
				case IN_SW_ISQL_EXTRACTTBL:
					istable = true;
					ret = EXTRACT;
					break;
#endif
				case IN_SW_ISQL_EXTRACT:
					ret = EXTRACT;
					break;

				case IN_SW_ISQL_EXTRACTALL:
					ret = EXTRACTALL;
					break;

				case IN_SW_ISQL_BAIL:
					setValues.BailOnError = true;
					break;

				case IN_SW_ISQL_ECHO:
					setValues.Echo = true;
					break;

				case IN_SW_ISQL_MERGE:
					Merge_stderr = true;
					break;

				case IN_SW_ISQL_MERGE2:
					Merge_diagnostic = true;
					break;

				case IN_SW_ISQL_NOAUTOCOMMIT:
					setValues.Autocommit = false;
					break;

				case IN_SW_ISQL_NODBTRIGGERS:
					Nodbtriggers = true;
					break;

				case IN_SW_ISQL_NOWARN:
					setValues.Warnings = false;
					break;

				case IN_SW_ISQL_OUTPUT:
					if (newoutput(swarg_str) == FAIL) {
						ret = ps_ERR;
					}
					break;

				case IN_SW_ISQL_INPUT:
					if (newinput(swarg_str) == SKIP) {
						Interactive = false;
					}
					else {
						ret = ps_ERR;
					}
					// CVC: Set by newinput() above only if successful.
					// Input_file = true;
					break;

				case IN_SW_ISQL_TERM:
					isqlGlob.Termlen = strlen(swarg_str);
					if (isqlGlob.Termlen >= MAXTERM_SIZE) {
						isqlGlob.Termlen = MAXTERM_SIZE - 1;
					}
					fb_utils::copy_terminate(isqlGlob.global_Term, swarg_str, isqlGlob.Termlen + 1);
					break;

				case IN_SW_ISQL_DATABASE:
					fb_utils::copy_terminate(isqlGlob.global_Target_db, swarg_str, sizeof(isqlGlob.global_Target_db));
					break;

				case IN_SW_ISQL_PAGE:
					if (swarg_int < 0)
					{
						IUTILS_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << s);
						STDERROUT(errbuf);
						ret = ps_ERR;
					}
					else if (swarg_int == 0) // let's interpret -pag 0 as SET HEADING OFF
						setValues.Heading = false;
					Pagelength = swarg_int;
					break;

				case IN_SW_ISQL_PASSWORD:
					fb_utils::copy_terminate(Password, fb_utils::get_passwd(swarg_str), sizeof(Password));
					// make sure swarg_str is really a pointer to argv, not something else !!
					global_psw = true;
					break;

				case IN_SW_ISQL_FETCHPASS:
				{
					const char* pass = NULL;
					const fb_utils::FetchPassResult rez = fb_utils::fetchPassword(swarg_str, pass);
					if (rez == fb_utils::FETCH_PASS_OK)
					{
						fb_utils::copy_terminate(Password, pass, sizeof(Password));
						global_psw = true;
					}
					else
					{
						switch (rez)
						{
						case fb_utils::FETCH_PASS_FILE_OPEN_ERROR:
							IUTILS_msg_get(PASS_FILE_OPEN, errbuf, SafeArg() << swarg_str << errno);
							// could not open password file @1, errno @2
							break;
						case fb_utils::FETCH_PASS_FILE_READ_ERROR:
							IUTILS_msg_get(PASS_FILE_READ, errbuf, SafeArg() << swarg_str << errno);
							// could not read password file @1, errno @2
							break;
						case fb_utils::FETCH_PASS_FILE_EMPTY:
							IUTILS_msg_get(EMPTY_PASS, errbuf, SafeArg() << swarg_str);
							// empty password file @1
							break;
						}
						STDERROUT(errbuf);
						ret = ps_ERR;
					}
					break;
				}

				case IN_SW_ISQL_USER:
					fb_utils::copy_terminate(isqlGlob.User, swarg_str, sizeof(isqlGlob.User));
					global_usr = true;
					break;

				case IN_SW_ISQL_ROLE:
					fb_utils::copy_terminate(isqlGlob.Role, swarg_str, sizeof(isqlGlob.Role));
					global_role = true;
					break;

				case IN_SW_ISQL_ROLE2:
					strcpy(isqlGlob.Role, "\"");
					fb_utils::copy_terminate(isqlGlob.Role + 1, swarg_str, sizeof(isqlGlob.Role) - 2);
					strcat(isqlGlob.Role, "\"");
					global_role = true;
					break;

				case IN_SW_ISQL_CACHE:
					if (swarg_int <= 0)
					{
						IUTILS_msg_get(USAGE_RANGE, errbuf, SafeArg() << swarg_str << s);
						STDERROUT(errbuf);
						ret = ps_ERR;
					}
					global_numbufs = swarg_int;
					has_global_numbufs = true;
					break;

				case IN_SW_ISQL_CHARSET:
					{
						fb_utils::copy_terminate(Charset, swarg_str, sizeof(Charset));
						const size_t lgth = strlen(Charset);
						if (lgth < MAXCHARSET_SIZE) {
							strcpy(setValues.ISQL_charset, Charset);
						}
						else
							fb_utils::copy_terminate(setValues.ISQL_charset, Charset, MAXCHARSET_SIZE);
					}
					break;

				case IN_SW_ISQL_QUIET:
					Quiet = true;
					break;

#ifdef TRUSTED_AUTH
				case IN_SW_ISQL_TRUSTED:
					Trusted_auth = true;
					break;
#endif

				case IN_SW_ISQL_VERSION:
					Version_info = true;
					IUTILS_msg_get(VERSION, errbuf, SafeArg() << FB_VERSION);
					isqlGlob.printf("%s%s", errbuf, NEWLINE);
					break;

				case IN_SW_ISQL_SQLDIALECT:
					requested_SQL_dialect = swarg_int;
					if (requested_SQL_dialect < SQL_DIALECT_V5 ||
						requested_SQL_dialect > SQL_DIALECT_CURRENT)
					{
						ret = ps_ERR;
					}
					else
					{
						// requested_SQL_dialect is used to specify the database dialect
						// if SQL dialect was not specified.  Since it is possible to
						// have a client dialect of 2, force the database dialect to 3, but
						// leave the client dialect as 2
						isqlGlob.SQL_dialect = requested_SQL_dialect;
						if (requested_SQL_dialect == SQL_DIALECT_V6_TRANSITION)
						{
							Merge_stderr = true;
							requested_SQL_dialect = SQL_DIALECT_V6;
						}
					}
					break;

				case IN_SW_ISQL_HELP:
					ret = ps_ERR;
					break;

				default: // unknown switch
					IUTILS_msg_get(SWITCH, errbuf, SafeArg() << s);
					STDERROUT(errbuf);
					ret = ps_ERR;

			}
		}
		else
		{
			// This is not a switch, it is a db_name
			if (isqlGlob.global_Db_name[0])
			{
				// We already have a database name
				IUTILS_msg_get(USAGE_DUPDB, errbuf, SafeArg() << isqlGlob.global_Db_name << s);
				STDERROUT(errbuf);
				ret = ps_ERR;
			}
			else
			{
				fb_utils::copy_terminate(isqlGlob.global_Db_name, s, sizeof(isqlGlob.global_Db_name));
#ifdef DEV_BUILD
				// If there is a table name, it follows
				if (istable && (++i < argc) && (s = argv[i]) && *s)
					fb_utils::copy_terminate(tabname, s, WORDLENGTH);
#endif
			}
		}

		// Quit the loop of interpreting if we got an error
		if (ret == ps_ERR)
			break;
	}

	// If not input, then set up first filelist

	if (Filelist->readingStdin())
	{
		Filelist->insert(stdin, "stdin", "stdin");
		fb_assert(Filelist->count() == 1);
	}

	return ret;
}


// *********************
// p a s s t h r o u g h
// *********************
// Execute a command directly on the server. No interpretation done in isql.
// Use with care. Only for debug builds.
#ifdef DEV_BUILD
static processing_state passthrough(const char* cmd)
{
	if (!DB)
		return FAIL;

	M__trans = DB->execute(fbStatus, M__trans, 0, cmd, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);
	if (ISQL_errmsg(fbStatus))
		return ps_ERR;

	return SKIP;
}
#endif

static bool checkSpecial(TEXT* const p, const int length, const double value)
{
/**************************************
 *
 *	c h e c k S p e c i a l
 *
 **************************************
 *
 * Functional description
 *	Special case - Nan and Infinity.
 *	Some libraries (SFIO) work wrong with them.
 *
 **************************************/
	const TEXT* t = NULL;
	if (isnan(value))
		t = "NaN";
	else if (isinf(value))
		t = Firebird::isNegativeInf(value) ? "-Infinity" : "Infinity";
	else
		return false;

	if (setValues.List) {
		isqlGlob.printf("%s%s", t, NEWLINE);
	}
	sprintf(p, "%*.*s ", length, length, t);

	return true;
}


static unsigned print_item(TEXT** s, const IsqlVar* var, const unsigned length)
{
/**************************************
 *
 *	p r i n t _ i t e m
 *
 **************************************
 *
 * Functional description
 *	Print an SQL data item as described.
 *
 **************************************/
 	TEXT d[32];

	TEXT* p = *s;
	*p = '\0';

	unsigned dtype = var->type;		// may be modified by print_item_blob()
	const int dscale = var->scale;

	if (setValues.List) {
		isqlGlob.printf("%-31s ", var->alias);
	}

	if (var->nullable && (*var->nullInd < 0))
	{
		// If field was missing print <null>

		if (setValues.List) {
			isqlGlob.printf("<null>%s", NEWLINE);
		}

		if (dtype == SQL_TEXT || dtype == SQL_VARYING || dtype == SQL_BOOLEAN)
			sprintf(p, "%-*.*s ", length, length, "<null>");
		else
			sprintf(p, "%*.*s ", length, length, "<null>");
	}
	else if (!strncmp(var->field, "DB_KEY", 6))
	{
		// Special handling for db_keys printed in hex

		// Keep a temp buf, d for building the binary string

		for (const TEXT* t = var->value.asChar; t < var->value.asChar + var->length; t++)
		{
			if (setValues.List) {
				isqlGlob.printf("%02X", (unsigned int) (UCHAR) *t);
			}
			else
			{
				sprintf(d, "%02X", (unsigned int) (UCHAR) *t);
				strcat(p, d);
			}
		}
		if (setValues.List)
			isqlGlob.printf(NEWLINE);
		else
			strcat(p, " ");
	}
	else
	{
		const ISC_QUAD* blobid;
		TEXT blobbuf[30];
		TEXT* str2;
		tm times;

		switch (dtype)
		{
		case SQL_ARRAY:

			// Print blob-ids only here

			blobid = var->value.blobid;
			sprintf(blobbuf, "%" xLONGFORMAT":%" xLONGFORMAT, blobid->gds_quad_high,
					blobid->gds_quad_low);
			sprintf(p, "%*s ", MAX(17, length), blobbuf);
			break;

		case SQL_BLOB:

			// Print blob-ids only here

			blobid = var->value.blobid;
			sprintf(blobbuf, "%" xLONGFORMAT":%" xLONGFORMAT, blobid->gds_quad_high,
					blobid->gds_quad_low);
			sprintf(p, "%*s ", MAX(17, length), blobbuf);
			if (setValues.List)
			{
				isqlGlob.printf("%s%s", blobbuf, NEWLINE);
				dtype = ISQL_print_item_blob(isqlGlob.Out, var, M__trans, setValues.Doblob);
				isqlGlob.printf(NEWLINE);
			}
			break;

		case SQL_SHORT:
		case SQL_LONG:
		case SQL_INT64:
			{
				SINT64 value;

				switch (dtype)
				{
				case SQL_SHORT:
					value = *var->value.asSmallint;
					break;
				case SQL_LONG:
					value = *var->value.asInteger;
					break;
				case SQL_INT64:
					value = *var->value.asBigint;
					break;
				}

				Firebird::string str_buf;
				print_item_numeric(value, length, dscale, str_buf.getBuffer(length));
				sprintf(p, "%s ", str_buf.c_str());
				if (setValues.List)
				{
					str_buf.ltrim(); // Added 1999-03-23 to left-justify in LIST ON mode
					isqlGlob.printf("%s%s", str_buf.c_str(), NEWLINE);
				}
			}
			break;

		case SQL_FLOAT:
			{
				//
				// BRS 08 Aug 2003
				// MSVC6 has a bug in the g format when used with # and display
				// one digit more than the specified precision when the value is 0
				// The bug appears in TCS DSQL_DOMAIN_12 and 13
				//
				const double value = *var->value.asFloat;

				if (checkSpecial(p, length, value))
				{
				    break;
				}

#if defined(MINGW)
				if (value == 0)
				{
					sprintf(p, "% #*.*g ", length, (int) MIN(8, (length - 6)) - 1, value);
					if (setValues.List) {
						isqlGlob.printf("%.*g%s", FLOAT_LEN - 6 -1, value, NEWLINE);
					}
				}
				else
				{
					sprintf(p, "% #*.*g ", length, (int) MIN(8, (length - 6)), value);
					if (setValues.List) {
						isqlGlob.printf("%.*g%s", FLOAT_LEN - 6, value, NEWLINE);
					}
				}
#else
				sprintf(p, "% #*.*g ", length, (int) MIN(8, (length - 6)), value);
				if (setValues.List) {
					isqlGlob.printf("%.*g%s", FLOAT_LEN - 6, value, NEWLINE);
				}
#endif
			}
			break;

		case SQL_DOUBLE:
			{
				const double value = *var->value.asDouble;

				if (checkSpecial(p, length, value))
				{
				    break;
				}

				// Don't let numeric/decimal doubles overflow print length
				// Special handling for 0 -- don't test log for length
				unsigned rounded = 0;
				if (dscale && (!value ||
					(rounded = static_cast<unsigned int>(ceil(fabs(log10(fabs(value)))))) < length - 10))
				{
					unsigned precision = 0;

					// CVC: Test values are
					// select -2.488355210669293e+01 from rdb$database;
					// select +2.488355210669293e+01 from rdb$database;
					// select +2.488355210669293e-01 from rdb$database;
					// select -2.488355210669293e-01 from rdb$database;
					// and sprintf should return length + 1.
					// See http://tracker.firebirdsql.org/browse/CORE-1363
					// The only way to enter this code is with a literal value (see above).
					// Taking SQL_DOUBLE from table fields or expressions will
					// yield dscale being zero.
					// This means that dscale is not what Borland expected; it's simply
					// the length of the incoming literal.
					if (value > 1)
						precision = length - rounded - 1; // nnn.nnn
					else if (value >= 0)
						precision = length - 2; // 0.nnn
					else if (value >= -1)
						precision = length - 3; // -0.nnn
					else // -nnn.nnn
						precision = length - rounded - 2;

					// Take into account old database containing negative scales for double
					if (dscale < 0 && precision > unsigned(-dscale))
					{
						precision = -dscale;
					}

					sprintf(p, "%*.*f ", length, precision, value);

					if (setValues.List) {
						isqlGlob.printf("%.*f%s", precision, /*dscale,*/ value, NEWLINE);
					}
				}
				else
				{
#if defined(MINGW)
					if (value == 0)
					{
						sprintf(p, "% #*.*g ", length, (int) MIN(16, (length - 7)) - 1, value);
						if (setValues.List) {
							isqlGlob.printf("%#.*g%s", DOUBLE_LEN - 7 - 1, value, NEWLINE);
						}
					}
					else
					{
						sprintf(p, "% #*.*g ", length, (int) MIN(16, (length - 7)), value);
						if (setValues.List) {
							isqlGlob.printf("%#.*g%s", DOUBLE_LEN - 7, value, NEWLINE);
						}
					}
#else
					sprintf(p, "% #*.*g ", length, (int) MIN(16, (length - 7)), value);
					if (setValues.List) {
						isqlGlob.printf("%#.*g%s", DOUBLE_LEN - 7, value, NEWLINE);
					}
#endif
				}
			}
			break;

		case SQL_TEXT:
			str2 = var->value.asChar;
			// See if it is character set OCTETS
			if (var->charSet == 1)
			{
				const ULONG hex_len = 2 * var->length;
				TEXT* buff2 = (TEXT*) ISQL_ALLOC(hex_len + 1);
				// Convert the string to hex digits
				for (unsigned i = 0; i < var->length; i++) {
					sprintf(&buff2[i * 2], "%02X", (UCHAR)str2[i]);
				}
				buff2[hex_len] = 0;
				if (setValues.List) {
					isqlGlob.printf("%-*s%s", var->length, buff2, NEWLINE);
				}
				else
					sprintf(p, "%-*s ", length, buff2);
				ISQL_FREE(buff2);
			}
			else if (setValues.List)
				isqlGlob.printf("%-*.*s%s", var->length, var->length, str2, NEWLINE);
			else
			{
				IcuUtil::pad(p, var->charSet,
					static_cast<unsigned>(strnlen(var->value.asChar, var->length)),
					var->value.asChar, length, false);
				strcat(p, " ");
			}
			break;

		case SQL_VARYING:
			{
				vary* avary = var->value.asVary;

				// If CHARACTER SET OCTETS, print two hex digits per octet
				if (var->charSet == 1)
				{
					const ULONG hex_len = 2 * avary->vary_length;
					char* buff2 = static_cast<char*>(ISQL_ALLOC(hex_len + 1));
					char* bp = buff2;

					for (unsigned i = 0; i < avary->vary_length; i++, bp += 2)
					{
						sprintf(bp, "%02X",
						static_cast<UCHAR>(avary->vary_string[i]));
					}
					buff2[hex_len] = '\0'; // there is an extra byte for terminator

					if (setValues.List)
					{
						isqlGlob.printf("%s%s", buff2, NEWLINE);
					}
					else
					{
						// Truncate if necessary
						sprintf(p, "%-*.*s ", length, static_cast<unsigned int>(MIN(length, hex_len)), buff2);
					}
					ISQL_FREE(buff2);
				}
				else if (setValues.List)
					isqlGlob.printf("%-*.*s%s", avary->vary_length, avary->vary_length, avary->vary_string, NEWLINE);
				else
				{
					IcuUtil::pad(p, var->charSet, avary->vary_length, avary->vary_string, length, false);
					strcat(p, " ");
				}
				break;
			}

		case SQL_TIMESTAMP:
			isc_decode_timestamp(var->value.asDateTime, &times);

			if (isqlGlob.SQL_dialect > SQL_DIALECT_V5)
			{
				sprintf(d, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d.%4.4" ULONGFORMAT,
						times.tm_year + 1900, (times.tm_mon + 1),
						times.tm_mday, times.tm_hour, times.tm_min,
						times.tm_sec,
						((ULONG*) var->value.asChar)[1] % ISC_TIME_SECONDS_PRECISION);
			}
			else
			{
				if (setValues.Time_display)
					sprintf(d, "%2.2d-%s-%4.4d %2.2d:%2.2d:%2.2d.%4.4" ULONGFORMAT,
							times.tm_mday, alpha_months[times.tm_mon],
							times.tm_year + 1900, times.tm_hour, times.tm_min,
							times.tm_sec,
							((ULONG*) var->value.asChar)[1] % ISC_TIME_SECONDS_PRECISION);
				else
					sprintf(d, "%2.2d-%s-%4.4d", times.tm_mday,
							alpha_months[times.tm_mon], times.tm_year + 1900);
			}

			sprintf(p, "%-*.*s ", length, length, d);
			if (setValues.List) {
				isqlGlob.printf("%s%s", d, NEWLINE);
			}
			break;

		case SQL_TYPE_TIME:
			isc_decode_sql_time(var->value.asTime, &times);
			sprintf(d, "%2.2d:%2.2d:%2.2d.%4.4" ULONGFORMAT,
					times.tm_hour, times.tm_min, times.tm_sec,
					(*var->value.asTime) % ISC_TIME_SECONDS_PRECISION);
			sprintf(p, "%-*.*s ", length, length, d);
			if (setValues.List) {
				isqlGlob.printf("%s%s", d, NEWLINE);
			}
			break;

		case SQL_TYPE_DATE:
			isc_decode_sql_date(var->value.asDate, &times);
			sprintf(d, "%4.4d-%2.2d-%2.2d", times.tm_year + 1900,
					(times.tm_mon + 1), times.tm_mday);

			sprintf(p, "%-*.*s ", length, length, d);
			if (setValues.List) {
				isqlGlob.printf("%s%s", d, NEWLINE);
			}
			break;

		case SQL_BOOLEAN:
			strcpy(d, (*var->value.asBoolean ? "<true>" : "<false>"));
			sprintf(p, "%-*.*s ", length, length, d);
			if (setValues.List)
				isqlGlob.printf("%s%s", d, NEWLINE);
			break;

		default:
			sprintf(d, "Unknown type: %d", dtype);
			sprintf(p, "%-*s ", length, d);
			if (setValues.List) {
				isqlGlob.printf("%s%s", d, NEWLINE);
			}
			break;
		}
	}

	while (*p)
		++p;

	*s = p;

	return dtype;
}


processing_state ISQL_print_item_blob(FILE* fp, const IsqlVar* var, Firebird::ITransaction* trans, int subtype)
{
/******************************************
 *
 *	I S Q L _ p r i n t _ i t e m _ b l o b
 *
 ******************************************
 *
 * Functional description
 *	Print a User Selected BLOB field (as oppposed to
 *	any BLOB selected in a show or extract command)
 *
 **************************************/
	TEXT msg[MSG_LENGTH];

	ISC_QUAD* blobid = var->value.blobid;

	// Don't bother with null blobs

	if (UserBlob::blobIsNull(*blobid))
		return CONT;

	if ((var->subType != subtype) && (subtype != ALL_BLOBS))
	{
		IUTILS_msg_get(BLOB_SUBTYPE, msg, SafeArg() << subtype << var->subType);
		// Blob display set to subtype %d. This blob: subtype = %d\n
		IUTILS_printf(fp, msg);
		return CONT;
	}

	USHORT bpb_length = 0;
	const UCHAR* bpb = NULL;
	UCHAR bpb_buffer[64];
	ISC_BLOB_DESC from_desc;

	const int blob_subtype = var->subType;
	if (blob_subtype == isc_blob_text)
	{
		// ASF: Since ODS11.1, BLOBs are automatically transliterated to the client charset.
		if (isqlGlob.major_ods < ODS_VERSION11 ||
			(isqlGlob.major_ods == ODS_VERSION11 && isqlGlob.minor_ods == 0))
		{
			// Lookup the remaining descriptor information for the BLOB field,
			// most specifically we're interested in the Character Set so
			// we can set up a BPB that requests character set transliteration

			// CVC: Adriano changed trans to D__trans with the following comment:
			// Fix a bug that occur when a BLOB column is added after the start
			// of the DML transaction.
			// ASF: But use M__trans if D__trans is not started;
			from_desc.blob_desc_subtype = var->subType;
			from_desc.blob_desc_charset = var->charSet;

			ISC_BLOB_DESC to_desc;
			ISC_STATUS_ARRAY oldStat;
			isc_blob_default_desc(&to_desc, (const UCHAR*) var->relation, (const UCHAR*) var->field);
			if (!isc_blob_gen_bpb(oldStat, &to_desc, &from_desc, sizeof(bpb_buffer),
								  bpb_buffer, &bpb_length))
			{
				bpb = bpb_buffer;
			}
		}
	}
	else if (blob_subtype > isc_blob_text && blob_subtype < isc_blob_max_predefined_subtype)
	{
		bpb = predefined_blob_subtype_bpb;
		bpb_length = sizeof(predefined_blob_subtype_bpb);
		set_bpb_for_translation(blob_subtype);
	}

	Firebird::IBlob* blob = DB->openBlob(fbStatus, trans, blobid, bpb_length, bpb);
	if (ISQL_errmsg(fbStatus))
	{
		return ps_ERR;
	}

	TEXT buffer[BUFFER_LENGTH512];

	do
	{
		unsigned int length;
		int cc = blob->getSegment(fbStatus, sizeof(buffer) - 1, buffer, &length);
		if (cc == Firebird::IStatus::RESULT_NO_DATA || cc == Firebird::IStatus::RESULT_ERROR)
			break;

		buffer[length] = 0;

		// Special displays for blr or acl subtypes

		if (blob_subtype > isc_blob_text && blob_subtype < isc_blob_max_predefined_subtype)
		{
			for (char* b = buffer + length - 1; b >= buffer;)
			{
				if (*b == '\n' || *b == '\t' || *b == BLANK)
					*b-- = 0;
				else
					break;
			}

			IUTILS_printf2(fp, "%s	%s%s", TAB_AS_SPACES, buffer, NEWLINE);
		}
		else
			IUTILS_printf(fp, buffer);

	} while (true);

	if (failed())
	{
		ISQL_errmsg(fbStatus);
		blob->close(fbStatus);
		return ps_ERR;
	}

	blob->close(fbStatus);
	return CONT;
}


static void print_item_numeric(SINT64 value, int length, int scale, TEXT* buf)
{
/**************************************
 *
 *	p r i n t _ i t e m _ n u m e r i c
 *
 **************************************
 *
 * Functional description
 *	Print a INT64 value into a buffer by accomodating
 *	decimal point '.' for scale notation
 *
 **************************************/
	// Handle special case of no negative scale, no '.' required!
	if (scale >= 0)
	{
		if (scale > 0)
			value *= (SINT64) pow(10.0, (double) scale);
		sprintf(buf, "%*" SQUADFORMAT, length, value);
		return;
	}

	const bool neg = (value < 0);

	// Use one less space than allowed, to leave room for '.'
	length--;
	sprintf(buf, "%*" SQUADFORMAT, length, value);

	// start from LSByte towards MSByte
	int from = length - 1;
	int to = length;

	// make space for decimal '.' point in buffer
	buf[to + 1] = '\0';
	for (; from >= 0 && DIGIT(buf[from]) && scale; from--, to--, ++scale)
		buf[to] = buf[from];

	// Check whether we need a '0' (zero) in front of the '.' point
	const bool all_digits_used = !DIGIT(buf[from]);

	// Insert new '0's to satisfy larger scale than input digits
	// For e.g: 12345 with scale -7 would be .0012345
	if (from > 0 && scale)
		do {
			buf[to--] = '0';
		} while (++scale != 0);

	// Insert '.' decimal point, and if required, '-' and '0'
	buf[to--] = '.';
	if (all_digits_used)
	{
		buf[to--] = '0';
		if (neg)
			buf[to--] = '-';
	}
}


static processing_state print_line(Firebird::IMessageMetadata* message, UCHAR* buf, const unsigned pad[], TEXT line[])
{
/**************************************
 *
 *	p r i n t _ l i n e
 *
 **************************************
 *
 * Functional description
 *	Print a line of SQL variables.
 *
 *	Args:  message, a message metadata
 *		pad = an array of the print lengths of all the columns
 *		line = pointer to the line buffer.
 *
 **************************************/
	const unsigned maxblob = 20;
	IsqlVar varlist[maxblob]; // No more than 20 blobs per line
	unsigned varnum = 0;

	{ // scope
		TEXT* p = line;
		unsigned n = message->getCount(fbStatus);
		if (ISQL_errmsg(fbStatus))
		{
			return ps_ERR;
		}
		for (unsigned i = 0; i < n; ++i)
		{
			IsqlVar var;
			if (ISQL_fill_var(&var, message, i, buf) == ps_ERR)
				return ps_ERR;

			if (!Interrupt_flag && !Abort_flag)
			{
				// Save all the blob vars and print them at the end
				// CVC: If varnum reaches 20, we must print an error instead of crashing.
				const int rc = print_item(&p, &var, pad[i]);
				if (rc == SQL_BLOB && varnum < maxblob)
					varlist[varnum++] = var;
			}
		}
		*p = 0;
	} // scope

	if (setValues.List)
	{
		isqlGlob.printf(NEWLINE);
		return (CONT);
	}

	isqlGlob.printf("%s%s", line, NEWLINE);

	// If blobdisplay is not wanted, set varnum back to 0

	if (setValues.Doblob == NO_BLOBS)
		varnum = 0;
	else if (varnum >= maxblob)
	{
		TEXT msg[MSG_LENGTH];
		IUTILS_msg_get(ONLY_FIRST_BLOBS, msg, SafeArg() << maxblob);
		isqlGlob.printf("%s%s", msg, NEWLINE);
	}

	// If there were Blobs to print, print them passing the blobid

	for (unsigned i = 0; i < varnum; i++)
	{
		const IsqlVar* var = &varlist[i];
		if (!var->nullable || *var->nullInd == 0)
		{
			// Print blob title

			isqlGlob.printf(
					"==============================================================================%s",
					NEWLINE);
			isqlGlob.printf("%s:  %s", var->alias, NEWLINE);
			if (ISQL_print_item_blob(isqlGlob.Out, var, M__trans, setValues.Doblob) != CONT)
				return (ps_ERR);
			isqlGlob.printf(
					"%s==============================================================================%s",
					NEWLINE, NEWLINE);
		}
	}
	return CONT;
}


// *********************************
// p r i n t _ p e r f o r m a n c e
// *********************************
// As the name implies, show performance as extracted from the API routines.
static processing_state print_performance(const SINT64* perf_before)
{
	// Translation of report strings. Do not remove "static" modifier.
	static bool have_report = false;
	static Firebird::GlobalPtr<Firebird::string> diag;

	SINT64 perf_after[ISQL_COUNTERS];
	Firebird::UtilInterfacePtr()->getPerfCounters(fbStatus, DB, ISQL_COUNTERS_SET, perf_after);
	if (ISQL_errmsg(fbStatus))
	{
		return ps_ERR;
	}

	if (!have_report)
	{
		TEXT report_1[MSG_LENGTH];
		IUTILS_msg_get(REPORT_NEW1, report_1);
		// Current memory = !\nDelta memory = !\nMax memory = !\nElapsed time = ~ sec\n
		diag->assign(report_1);

#ifndef WIN_NT
		IUTILS_msg_get(REPORT_NEW2, report_1);
		// Cpu = !u sec\n
		diag->append(report_1);
#endif

		IUTILS_msg_get(REPORT_NEW3, report_1);
		// Buffers = !b\nReads = !r\nWrites = !w\nFetches = !f\n
		diag->append(report_1);

		Firebird::string::size_type p;
		while ((p = diag->find('!')) != Firebird::string::npos)
			diag->replace(p, 1, "%" SQUADFORMAT);
		while ((p = diag->find('~')) != Firebird::string::npos)
			diag->replace(p, 1, "%" SQUADFORMAT".%.3" SQUADFORMAT);

		have_report = true;
	}

	struct IsqlStatist
	{
		SINT64 curMem, deltaMem, maxMem;
		SINT64 realTime, cpu;
		SINT64 buffers, reads, writes, fetches;

		// ISQL_COUNTERS_SET = "CurrentMemory, MaxMemory, RealTime, UserTime, Buffers, Reads, Writes, Fetches"
		//						0			   1		  2			3		  4		   5	  6		  7
		IsqlStatist(const SINT64* b, const SINT64* a)
		{
			curMem = a[0];
			deltaMem = a[0] - b[0];
			maxMem = a[1];
			realTime = a[2] - b[2];
			cpu = a[3] - b[3];
			buffers = a[4];
			reads = a[5] - b[5];
			writes = a[6] - b[6];
			fetches = a[7] - b[7];
		}
	};

	IsqlStatist iStat(perf_before, perf_after);

	// counters: "%" SQUADFORMAT
	// time: "%" SQUADFORMAT".%.3" SQUADFORMAT
	IUTILS_printf2(Diag, diag->c_str(), iStat.curMem, iStat.deltaMem, iStat.maxMem,
		iStat.realTime / 1000, iStat.realTime % 1000,
#ifndef WIN_NT
		iStat.cpu / 1000, iStat.cpu % 1000,
#endif
		iStat.buffers, iStat.reads, iStat.writes, iStat.fetches);
	IUTILS_printf2(Diag, "%s", NEWLINE);

	return CONT;
}


// *********************************
// p r i n t _ m e s s a g e
// *********************************
// Show the contents of the input msg, used typically for parameters.
static void print_message(Firebird::IMessageMetadata* msg, const char* dir)
{
	unsigned n = msg->getCount(fbStatus);
	isqlGlob.printf(
			"\n%sPUT message field count: %d\n", dir, n);
	for (unsigned i = 0; i < n; ++i)
	{
		unsigned type = msg->getType(fbStatus, i);
		unsigned subtype = msg->getSubType(fbStatus, i);
		unsigned cs;
		isqlGlob.printf("%02d: sqltype: %d %s %sscale: %d subtype: %d len: %d",
				i + 1, type, sqltype_to_string(type), msg->isNullable(fbStatus, i) ? "Nullable " : "",
				msg->getScale(fbStatus, i), subtype, msg->getLength(fbStatus, i));

		switch(type)
		{
		case SQL_BLOB:
			if (subtype != 1)
				break;

		case SQL_TEXT:
		case SQL_VARYING:
			cs = msg->getCharSet(fbStatus, i);
			isqlGlob.printf(" charset: %d %s", cs, charset_to_string(cs));
			break;
		}

		isqlGlob.printf("\n");
		isqlGlob.printf("  :  name: %s  alias: %s\n",
				msg->getField(fbStatus, i), msg->getAlias(fbStatus, i));
		isqlGlob.printf("  : table: %s  owner: %s\n",
				msg->getRelation(fbStatus, i), msg->getOwner(fbStatus, i));
	}
}


// ***************************
// p r o c e s s _ h e a d e r
// ***************************
// Write into buffers the information that will be printed as page header for
// statements that return data.
static void process_header(Firebird::IMessageMetadata* msg, const unsigned pad[], TEXT header[], TEXT header2[])
{
	// Create the column header :  Left justify strings

	TEXT* p = header;
	TEXT* p2 = header2;
	unsigned n_cols = msg->getCount(fbStatus);
	if (ISQL_errmsg(fbStatus))
	{
		return;
	}

	for (unsigned i = 0; i < n_cols; ++i)
	{
		IsqlVar var;
		if (ISQL_fill_var(&var, msg, i, NULL) == ps_ERR)
			return;

		const unsigned type = var.type;

		IcuUtil::pad(p, isqlGlob.att_charset, static_cast<unsigned>(strlen(var.alias)), var.alias, pad[i],
			(type != SQL_TEXT && type != SQL_VARYING));
		strcat(p, " ");

		p += strlen(p);

		// Separators need not go on forever no more than a line
		unsigned limit = IcuUtil::charLength(
			isqlGlob.att_charset, static_cast<unsigned>(strlen(var.alias)), var.alias);
		limit = MAX(limit, pad[i]) + 1;

		for (unsigned j = 1; j < limit && j < 80; j++)
			*p2++ = '=';
		*p2++ = BLANK;
	}
	*p2 = '\0';
}


// ***********************
// p r o c e s s _ p l a n
// ***********************
// Retrieve and show the server's execution plan.
// We don't consider critical a failure to get the plan, so we don't return
// any result to the caller.
static void process_plan()
{
	if (!global_Stmt)
		return;

	// Bug 7565: A plan larger than plan_buffer will not be displayed
	// Bug 7565: Note also that the plan must fit into Print_Buffer

	UCHAR plan_info[1];
	plan_info[0] = setValues.ExplainPlan ? isc_info_sql_explain_plan : isc_info_sql_get_plan;

	Firebird::HalfStaticArray<UCHAR, MAX_SSHORT> planBuffer;
	unsigned planSize = MAX_SSHORT;
	UCHAR* planPtr = planBuffer.getBuffer(planSize);

	Firebird::string planString;

	for (int i = 0; i < 2; ++i)
	{
		global_Stmt->getInfo(fbStatus, sizeof(plan_info), plan_info, planSize, planPtr);
		if (ISQL_errmsg(fbStatus))
			return;

		bool truncated = false;

		for (const UCHAR* ptr = planPtr; ptr < planPtr + planSize;)
		{
			const UCHAR tag = *ptr++;

			switch (tag)
			{
			case isc_info_sql_get_plan:
			case isc_info_sql_explain_plan:
				{
					const USHORT len = (USHORT) gds__vax_integer(ptr, sizeof(USHORT));
					ptr += sizeof(USHORT);
					planString.assign((const char*) ptr, len);
					ptr += len;
				}
				break;
			case isc_info_truncated:
				truncated = true;
				break;
			case isc_info_end:
				break;
			default:
				IUTILS_printf2(Diag, "Unknown error while retrieving plan%s", NEWLINE);
				return;
			}

			if (tag == isc_info_end || tag == isc_info_truncated)
				break;
		}

		if (truncated && i == 0 &&
			// Probably FB 2.1 and before will crash with MAX_USHORT
			ENCODE_ODS(isqlGlob.major_ods, isqlGlob.minor_ods) >= ODS_11_2)
		{
			planSize = MAX_USHORT;
			planBuffer.resize(planSize);
			planPtr = planBuffer.begin();
			continue;
		}

		break;
	}

	if (planString.hasData())
		IUTILS_printf2(Diag, "%s%s", planString.c_str(), NEWLINE);
}


// ***************************************
// p r o c e s s _ r e c o r d _ c o u n t
// ***************************************
// Get number of records affected for updates, deletions and insertions.
// Return -1 if the statement is not one of those or if there's an error
// retrieving the information from the server.
static SINT64 process_record_count(const int statement_type)
{
	if (!global_Stmt)
		return -1;
	UCHAR count_type = 0;

	// Skip selects, better to count records incoming later
	switch (statement_type)
	{
	case isc_info_sql_stmt_update:
		count_type = isc_info_req_update_count;
		break;
	case isc_info_sql_stmt_delete:
		count_type = isc_info_req_delete_count;
		break;
	case isc_info_sql_stmt_insert:
		count_type = isc_info_req_insert_count;
		break;
	}

	if (count_type)
	{
		const UCHAR count_info[] = { isc_info_sql_records };
		UCHAR count_buffer[33];
		global_Stmt->getInfo(fbStatus, sizeof(count_info), count_info,
			sizeof(count_buffer), count_buffer);
		if (ISQL_errmsg(fbStatus))
		{
			return -1;
		}

		if (count_buffer[0] == isc_info_sql_records)
		{
			SINT64 total = 0;
			const UCHAR* p = count_buffer + 3;
			while (*p != isc_info_end)
			{
				const UCHAR count_is = *p++;
				const SSHORT len = gds__vax_integer(p, 2);
				p += 2;
				const ULONG count = gds__vax_integer(p, len);
				p += len;
				// CVC: Do as StatementMetadata::getAffectedRecords() does:
				//if (count_is == count_type)
				//	return count;
				if (count_is != isc_info_req_select_count)
					total += count;
			}
			return total;
		}
	}
	return -1;
}


// ***************************************
// p r o c e s s _ r e q u e s t _ t y p e
// ***************************************
// Retrieve the statement type according to the DSQL layer.
// A failure is indicated by returning zero.
static int process_request_type()
{
	if (!global_Stmt)
		return 0;
	const UCHAR sqlda_info[] = { isc_info_sql_stmt_type };
	UCHAR info_buffer[16];
	global_Stmt->getInfo(fbStatus, sizeof(sqlda_info), sqlda_info, sizeof(info_buffer), info_buffer);
	if (!ISQL_errmsg(fbStatus))
	{
		if (info_buffer[0] == isc_info_sql_stmt_type)
		{
		    const UCHAR* b = info_buffer;
			const SSHORT len = gds__vax_integer(b + 1, 2);
			return gds__vax_integer(b + 3, len);
		}

		//IUTILS_msg_get(UNKNOWN_STATEMENT_TYPE, errmsg);
		STDERROUT("Cannot determine statement type");
	}
	return 0;
}


// *********************************************
// p r o c e s s _ m e s s a g e _ d i s p l a y
// *********************************************
// Calculate individual column widths and return total line width.
static unsigned process_message_display(Firebird::IMessageMetadata* message, unsigned pad[])
{
	unsigned linelength = 0;
	unsigned ncols = message->getCount(fbStatus);
	if (ISQL_errmsg(fbStatus))
	{
		return 0;
	}
	for (unsigned i = 0; i < ncols; ++i)
	{
		IsqlVar var;
		if (ISQL_fill_var(&var, message, i, NULL) == ps_ERR)
			return 0;

		// Record the length of name and var, then convert to print
		// length, later to be stored in pad array

		unsigned data_length, disp_length, alignment;
		data_length = disp_length = alignment = var.length;
		unsigned namelength = IcuUtil::charLength(isqlGlob.att_charset,
			static_cast<unsigned>(strlen(var.alias)), var.alias);

		// Minimum display length should not be less than that needed
		// for displaying null

		if (namelength < NULL_DISP_LEN)
			namelength = NULL_DISP_LEN;

		const unsigned type = var.type;
		const SSHORT charSet = TTYPE_TO_CHARSET(var.charSet);

		switch (type)
		{
		case SQL_BLOB:
		case SQL_ARRAY:
			// enough room for the blob id to print

			disp_length = 17;
			break;
		case SQL_TIMESTAMP:
			if (setValues.Time_display || isqlGlob.SQL_dialect > SQL_DIALECT_V5)
				disp_length = DATETIME_LEN;
			else
				disp_length = DATE_ONLY_LEN;
			break;
		case SQL_TYPE_TIME:
			disp_length = TIME_ONLY_LEN;
			break;
		case SQL_TYPE_DATE:
			disp_length = DATE_ONLY_LEN;
			break;
		case SQL_FLOAT:
			disp_length = FLOAT_LEN;
			break;
		case SQL_DOUBLE:
			disp_length = DOUBLE_LEN;
			break;
		case SQL_TEXT:
			alignment = 1;
			data_length++;
			// OCTETS data is displayed in hex
			if (charSet == CS_BINARY)
				disp_length = 2 * var.length;
			else if (charSet == CS_UNICODE_FSS)
				disp_length /= 3;
			else if (charSet == CS_UTF8)
				disp_length /= 4;
			break;
		case SQL_VARYING:
			data_length += sizeof(USHORT) + 1;
			alignment = sizeof(USHORT);
			// OCTETS data is displayed in hex
			if (charSet == CS_BINARY)
				disp_length = 2 * var.length;
			else if (charSet == CS_UNICODE_FSS)
				disp_length /= 3;
			else if (charSet == CS_UTF8)
				disp_length /= 4;
			break;
		case SQL_SHORT:
			disp_length = SHORT_LEN;
			break;
		case SQL_LONG:
			disp_length = LONG_LEN;
			break;
		case SQL_INT64:
			disp_length = INT64_LEN;
			break;
		case SQL_BOOLEAN:
			disp_length = BOOLEAN_LEN;
			break;
		default:
			disp_length = UNKNOWN_LEN;
			break;
		}

		// special case db_key alignment which arrives as an SQL_TEXT

		if (!strncmp(var.field, "DB_KEY", 6))
		{
			alignment = 8;
			disp_length = 2 * var.length;
		}

		// This is the print width of each column

		if (disp_length < namelength)
			disp_length = namelength;

		pad[i] = disp_length;

		// Is there a collist entry, then use that width, but only for text
		if (type == SQL_TEXT || type == SQL_VARYING)
		{
			if (!setValues.global_Cols.find(var.alias, &pad[i]) && setValues.global_Col_default)
				pad[i] = setValues.global_Col_default;
			disp_length = pad[i];

			if (charSet == CS_UNICODE_FSS)
				disp_length *= 3;
			else if (charSet == CS_UTF8)
				disp_length *= 4;
		}
		else if (isqlGlob.att_charset == CS_UNICODE_FSS)
			disp_length = MAX(disp_length, namelength * 3);
		else if (isqlGlob.att_charset == CS_UTF8)
			disp_length = MAX(disp_length, namelength * 4);

		// The total line length
		linelength += disp_length + 1;
	}

	return linelength;
}


static processing_state process_statement(const TEXT* str2)
{
/**************************************
 *
 *	p r o c e s s _ s t a t e m e n t
 *
 **************************************
 *
 * Functional description
 *	Prepare and execute a dynamic SQL statement.
 *	This function uses the isc_dsql user functions rather
 *	than embedded dynamic statements.  The user request
 *	is placed on transaction M__trans, while all
 *	background work is on the default fbTrans.
 *	This function now returns CONT (success) or ps_ERR.
 **************************************/

	// Here we actively use the fact that fb_cancel_enable/disable commands may be send many times.
	// They are ignored if cancel already has requested state. This let's us disable cancel
	// in the middle of processing, but do not care about special scope for CancelHolder variable.
	class CancelHolder
	{
	public:
		CancelHolder()
		{
			if (DB)
				DB->cancelOperation(fbStatus, fb_cancel_enable);
		}
		~CancelHolder()
		{
			if (DB)
				DB->cancelOperation(fbStatus, fb_cancel_disable);
		}
	};

	processing_state ret = CONT;

	// enable CANCEL during statement processing
	CancelHolder cHolder;

	// If somebody did a commit or rollback, we are out of a transaction

	if (!M_Transaction())
		return ps_ERR;

	// No need to start a default transaction unless there is no current one

	if (setValues.Autocommit)
	{
		if (!D_Transaction())
			return ps_ERR;
	}

	// If statistics are requested, then reserve them here

	SINT64 perf_before[ISQL_COUNTERS];
	if (setValues.Stats)
	{
		Firebird::UtilInterfacePtr()->getPerfCounters(fbStatus,
			DB, ISQL_COUNTERS_SET, perf_before);
		if (ISQL_errmsg(fbStatus))
		{
			return ps_ERR;
		}
	}

	// Prepare the dynamic query stored in string.
	// But put this on the DDL transaction to get maximum visibility of
	// metadata.

	Firebird::ITransaction* prepare_trans = setValues.Autocommit ? D__trans : M__trans;

	if (global_Stmt)
	{
		global_Stmt->free(fbStatus);
		if (ISQL_errmsg(fbStatus))
			return (SKIP);
	}

	global_Stmt = DB->prepare(fbStatus, prepare_trans, 0, str2, isqlGlob.SQL_dialect,
		Firebird::IStatement::PREPARE_PREFETCH_METADATA);
	if (failed())
	{
		if (isqlGlob.SQL_dialect == SQL_DIALECT_V6_TRANSITION && Input_file)
		{
			isqlGlob.printf("%s%s%s%s%s%s",
							NEWLINE,
							"**** Error preparing statement:",
							NEWLINE,
							NEWLINE,
							str2,
							NEWLINE);
		}
		ISQL_errmsg(fbStatus);
		return ps_ERR;
	}

	// check for warnings
	ISQL_warning(fbStatus);

	// Find out what kind of statement this is
	const int statement_type = process_request_type();
	if (!statement_type)
		return ps_ERR;

	if (setValues.Sqlda_display)
	{
		const bool can_have_input_parameters =
			statement_type == isc_info_sql_stmt_select ||
			statement_type == isc_info_sql_stmt_insert ||
			statement_type == isc_info_sql_stmt_update ||
			statement_type == isc_info_sql_stmt_delete ||
			statement_type == isc_info_sql_stmt_exec_procedure ||
			statement_type == isc_info_sql_stmt_select_for_upd ||
			statement_type == isc_info_sql_stmt_get_segment;

		if (can_have_input_parameters)
		{
			Firebird::RefPtr<Firebird::IMessageMetadata>
				input(Firebird::REF_NO_INCR, global_Stmt->getInputMetadata(fbStatus));
			if (ISQL_errmsg(fbStatus))
			{
				return ps_ERR;
			}

			// check for warnings
			ISQL_warning(fbStatus);

			print_message(input, "IN");
		}
	}


	const bool is_selectable =
		statement_type == isc_info_sql_stmt_select ||
		statement_type == isc_info_sql_stmt_select_for_upd ||
		statement_type == isc_info_sql_stmt_exec_procedure ||
		statement_type == isc_info_sql_stmt_get_segment;

	// if PLAN is set and this is not DDL, print out the plan now

	if (setValues.Plan && statement_type != isc_info_sql_stmt_ddl &&
		statement_type != isc_info_sql_stmt_set_generator)
	{
		process_plan();
		if (setValues.Planonly && !is_selectable)
		{
			return ret;	// do not execute
		}
	}

	// If the statement isn't a select, execute it and be done

	if (!is_selectable && !setValues.Planonly)
	{

		// If this is an autocommit, put the DDL stmt on a special trans

		if (setValues.Autocommit &&
			(statement_type == isc_info_sql_stmt_ddl ||
			 statement_type == isc_info_sql_stmt_set_generator))
		{
			DB->execute(fbStatus, D__trans, 0, str2, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);
			if (ISQL_errmsg(fbStatus))
			{
				ret = ps_ERR;
			}
			else
			{
				// check for warnings
				ISQL_warning(fbStatus);
			}

			// CVC: DDL statements that aren't syntax errors are caught by DFW
			// only at commit time, so we need to check here.
			// AP: fbStatus will be cleaned in commit_trans()
			if (!commit_trans(&D__trans))
				ret = ps_ERR;

			// check for warnings from COMMIT
			ISQL_warning(fbStatus);

			if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
				ret = ps_ERR;
			return ret;
		}

		// Check to see if this is a SET TRANSACTION statement

		if (statement_type == isc_info_sql_stmt_start_trans)
		{
			// CVC: Starting a txn can fail, too. Let's check it, although I
			// suspect isql will catch it in frontend_set() through get_statement(),
			// so this place has little chance to be reached.
			if (newtrans(str2) == FAIL)
				return ps_ERR;

			if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
				ret = ps_ERR;
			return ret;
		}

		//  This is a non-select DML statement or trans

		M__trans = global_Stmt->execute(fbStatus, M__trans, NULL, NULL, NULL, NULL);
		if (ISQL_errmsg(fbStatus))
		{
			// CVC: Make this conditional if it causes problems. For example
			// if (setValues.BailOnError)
			ret = ps_ERR;
		}

		// check for warnings
		ISQL_warning(fbStatus);

		// We are executing a commit or rollback, commit default trans

		if ((statement_type == isc_info_sql_stmt_commit) ||
			(statement_type == isc_info_sql_stmt_rollback))
		{
			// CVC: Commit may fail with AUTO-DDL off and DDL changes rejected by DFW.
			if (D__trans && !commit_trans(&D__trans))
				ret = ps_ERR;
		}

		// Print statistics report

		if (setValues.Docount)
		{
			const SINT64 count = process_record_count(statement_type);
			if (count >= 0)
			{
				TEXT rec_count_msg[MSG_LENGTH];
				IUTILS_msg_get(REC_COUNT, rec_count_msg, SafeArg() << count);
				// Records affected: %ld
				isqlGlob.printf("%s%s", rec_count_msg, NEWLINE);
			}
		}

		if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
			ret = ps_ERR;
		return ret;
	}

	Firebird::RefPtr<Firebird::IMessageMetadata>
		message(Firebird::REF_NO_INCR, global_Stmt->getOutputMetadata(fbStatus));
	if (ISQL_errmsg(fbStatus))
	{
		return ps_ERR;
	}

	// check for warnings
	ISQL_warning(fbStatus);

	const unsigned n_cols = message->getCount(fbStatus);
	if (ISQL_errmsg(fbStatus))
	{
		return ps_ERR;
	}

	// To facilitate debugging, the option
	// SET SQLDA_DISPLAY ON
	// will activate code to display the SQLDA after each statement.

	if (setValues.Sqlda_display)
		print_message(message, "OUT");

	if (setValues.Planonly)
		return ret;

	unsigned bufLen = message->getMessageLength(fbStatus);
	if (ISQL_errmsg(fbStatus))
	{
		return ps_ERR;
	}
	UCHAR* buffer = global_Buffer->getBuffer(bufLen);

	// Pad is an array of lengths to be passed to the print_item
	unsigned* pad = NULL;
	if (n_cols) {
		pad = (unsigned*) ISQL_ALLOC ((SLONG) (n_cols * sizeof(int)));
	}

	// Calculate display width and add a few for line termination, et al
    const SLONG linelength = process_message_display(message, pad) + 10;

	// Allocate the print line, the header line and the separator

	TEXT* line = (TEXT*) ISQL_ALLOC(linelength);
	TEXT* header = 0;
	TEXT* header2 = 0;

	if (setValues.Heading)
	{
		header = (TEXT*) ISQL_ALLOC(linelength);
		header2 = (TEXT*) ISQL_ALLOC(linelength);
		*header = '\0';
		*header2 = '\0';

		process_header(message, pad, header, header2);
	}

	// If this is an exec procedure, execute into the buffer with one fetch only

	if (statement_type == isc_info_sql_stmt_exec_procedure)
	{
		global_Stmt->execute(fbStatus, M__trans, NULL, NULL, message, buffer);
		if (ISQL_errmsg(fbStatus))
		{
			ret = ps_ERR;
		}
		else
		{
			if (n_cols)
			{
				// do not output unnecessary white text
				isqlGlob.printf(NEWLINE);
				if (!setValues.List && setValues.Heading)
				{
					isqlGlob.printf("%s%s%s%s",
									header,
									NEWLINE,
									header2,
									NEWLINE);
				}
				print_line(message, buffer, pad, line);
				isqlGlob.printf(NEWLINE);
			}
		}
	}
	else
	{
		// Otherwise, open the cursor to start things up

		Firebird::IResultSet* curs = global_Stmt->openCursor(fbStatus, M__trans,
			NULL, NULL, message, 0);
		if (ISQL_errmsg(fbStatus))
		{
			return ps_ERR;
		}

		// check for warnings
		ISQL_warning(fbStatus);

		// Now fetch and print records until EOF

#ifdef HAVE_TERMIOS_H
		int out_fd = fileno(isqlGlob.Out);
		if (isatty(out_fd))
		{
			struct termios tflags;
			if (tcgetattr(out_fd, &tflags) == 0)
			{
				tflags.c_lflag |= NOFLSH;
				tcsetattr(out_fd, TCSANOW, &tflags);
			}
		}
#endif

		const bool printHead = !setValues.List && setValues.Heading;
		unsigned int lines;
		for (lines = 0; !Interrupt_flag && !Abort_flag; ++lines)
		{

			// Check if exceeded setValues.maxRows value
			if (setValues.maxRows != 0 && lines >= setValues.maxRows)
				break;

			// Fetch the current cursor

			if (curs->fetchNext(fbStatus, buffer) == Firebird::IStatus::RESULT_NO_DATA)
				break;

			// Print the header every Pagelength number of lines for
			// command-line ISQL only.

			if (printHead &&
				(Pagelength && (lines % Pagelength == 0) ||
				 !Pagelength && !lines) )
			{
				isqlGlob.printf("%s%s%s%s%s",
								NEWLINE,
								header,
								NEWLINE,
								header2,
								NEWLINE);
			}

			if (!lines && !printHead)
				isqlGlob.printf(NEWLINE);

			if (ISQL_errmsg(fbStatus))
			{
				ret = ps_ERR;
				break;
			}

			// check for warnings
			ISQL_warning(fbStatus);

			ret = print_line(message, buffer, pad, line);
		}

		if (lines)
			isqlGlob.printf(NEWLINE);

		// Record count printed here upon request

		if (setValues.Docount)
		{
			TEXT rec_count_msg[MSG_LENGTH];
			IUTILS_msg_get(REC_COUNT, rec_count_msg, SafeArg() << lines);
			// Records affected: @1
			isqlGlob.printf("%s%s", rec_count_msg, NEWLINE);
		}

		curs->close(fbStatus);
	}

	// Avoid cancel during cleanup
	DB->cancelOperation(fbStatus, fb_cancel_disable);

	// Statistics printed here upon request

	if (setValues.Stats && (print_performance(perf_before) == ps_ERR))
		ret = ps_ERR;

	if (pad)
		ISQL_FREE(pad);
	if (line)
		ISQL_FREE(line);
	if (header)
		ISQL_FREE(header);
	if (header2)
		ISQL_FREE(header2);

	return (ret);
}


#ifdef WIN_NT
static BOOL CALLBACK query_abort(DWORD dwCtrlType)
#else
static int query_abort(const int reason, const int, void*)
#endif
{
/**************************************
 *
 *	q u e r y _ a b o r t
 *
 **************************************
 *
 * Functional description
 *	Signal handler for interrupting output of a query.
 *  Note: this function is currently used in completelly different ways in Windows x POSIX.
 *
 **************************************/

	bool flag = true;

#ifdef WIN_NT
	if (dwCtrlType != CTRL_C_EVENT)
		return FALSE;
#else
	if (reason != fb_shutrsn_signal)
	{
		return FB_SUCCESS;
	}
#endif

	if (DB)
	{
		Firebird::LocalStatus ls;
		Firebird::CheckStatusWrapper status(&ls);
		DB->cancelOperation(&status, fb_cancel_raise);
		flag = !(status.getState() & Firebird::IStatus::STATE_ERRORS);
	}

	if (flag)
	{
		if (Interactive)
			Interrupt_flag = true;
		else
			Abort_flag = true;
	}

#ifdef WIN_NT
	return TRUE;
#else
	// we do not want to proceed with shutdown except when exit() was called
	return FB_FAILURE;
#endif
}


// Detect if stdin is redirected, IE we aren't reading from the console.
static bool stdin_redirected()
{
#ifdef WIN_NT
	HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
	const DWORD file_type = GetFileType(in);
	if (file_type == FILE_TYPE_CHAR || file_type == FILE_TYPE_PIPE)
		return false;
#else
	if (isatty(fileno(stdin)))
		return false;
#endif
	return true;
}


// CVC: There's something either wrong or on purpose in this routine:
// it doesn't unescape the embedded quotes that may exist.
// But it's useful for paths as it's now.
static void strip_quotes(const TEXT* in, TEXT* out)
{
/**************************************
 *
 *	s t r i p _ q u o t e s
 *
 **************************************
 *
 * Functional description
 *	Get rid of quotes around strings
 *
 **************************************/
	if (!in || !*in)
	{
		*out = 0;
		return;
	}

	TEXT quote = 0;
	// Skip any initial quote
	if ((*in == DBL_QUOTE) || (*in == SINGLE_QUOTE))
		quote = *in++;
	const TEXT* p = in;

	// Now copy characters until we see the same quote or EOS
	while (*p && (*p != quote)) {
		*out++ = *p++;
	}
	*out = 0;
}


static const char* sqltype_to_string(unsigned sqltype)
{
/**************************************
 *
 *	s q l t y p e _ t o _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Return a more readable version of SQLDA.sqltype
 *
 **************************************/
	switch (sqltype)
	{
	case SQL_TEXT:
		return "TEXT";
	case SQL_VARYING:
		return "VARYING";
	case SQL_SHORT:
		return "SHORT";
	case SQL_LONG:
		return "LONG";
	case SQL_INT64:
		return "INT64";
	case SQL_FLOAT:
		return "FLOAT";
	case SQL_DOUBLE:
		return "DOUBLE";
	case SQL_D_FLOAT:
		return "D_FLOAT";
	case SQL_TIMESTAMP:
		return "TIMESTAMP";
	case SQL_TYPE_DATE:
		return "SQL DATE";
	case SQL_TYPE_TIME:
		return "TIME";
	case SQL_BLOB:
		return "BLOB";
	case SQL_ARRAY:
		return "ARRAY";
	case SQL_QUAD:
		return "QUAD";
	case SQL_BOOLEAN:
		return "BOOLEAN";
	case SQL_NULL:
		return "NULL";
	default:
		return UNKNOWN;
	}
}


static const char* charset_to_string(unsigned charset)
{
/**************************************
 *
 *	s q l t y p e _ t o _ s t r i n g
 *
 **************************************
 *
 * Functional description
 *	Return a more readable version of SQLDA.sqltype
 *
 **************************************/
	static Firebird::GlobalPtr<Firebird::GenericMap<Firebird::Pair<Firebird::Right<unsigned, Firebird::string> > > > csMap;

	charset = TTYPE_TO_CHARSET(charset);

	Firebird::string* text = csMap->get(charset);
	if (text)
		return text->c_str();

	csMap->clear();
	bool err = false;

	if (!frontendTransaction())
		return UNKNOWN;

	FOR CS IN RDB$CHARACTER_SETS
		if (!err)
		{
			try
			{
				fb_utils::exact_name(CS.RDB$CHARACTER_SET_NAME);
				csMap->put(CS.RDB$CHARACTER_SET_ID, CS.RDB$CHARACTER_SET_NAME);
			}
			catch (const Firebird::Exception& ex)
			{
				err = true;
				ex.stuffException(fbStatus);
				ISQL_errmsg(fbStatus);
			}
		}
	END_FOR
	ON_ERROR
		ISQL_errmsg(fbStatus);
	END_ERROR;

	text = csMap->get(charset);
	if (text)
		return text->c_str();

	return UNKNOWN;
}
