
/* FastBase MySQL + TCL interface program; created 22nd Sept 2000 */
/* This program was written after extensive use of the various mysql-tcl */
/* interface programs around, hopefully this program addresses some of */
/* the issues I had with those programs - especially in relation to */
/* writing the FastBase accounting system which utilises large queries */
/* and requires the highest performance levels available */

/* fbsql provides simple access to the mysql database, the commands are */
/* very similar to the other tcl-mysql interfaces */
/* added support for handling large queries without using client memory */
/* added support for transferring results to arrays for convenience/speed */

/* Modified to handle "high" characters (> 0x7f)  */

/* Copyright 2003, Peter Campbell Software Ltd. http://www.fastbase.co.nz, mailto:pc@acs.co.nz */

/* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: */

/* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. */
/* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer */
/* in the documentation and/or other materials provided with the distribution. */
/* Neither the name of Peter Campbell Software nor the names of its contributors may be used to endorse or promote products */
/* derived from this software without specific prior written permission. */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED */
/* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A */
/* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR */
/* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) */
/* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED */
/* OF THE POSSIBILITY OF SUCH DAMAGE. */

#include <stdlib.h>
#include <string.h>

#ifdef WINDOWS
#include <WINDOWS.H>
#include <winsock.h>
#endif

#include <mysql.h>
#include <tcl.h>

char errormsg[1000];
int done;

#define SQL_COMMANDS 11
#define UTF_ENCODING 0

struct mysql_connect
{
	int CONNECTED;
	int query_flag;
	int NUMROWS;
	int use_array;
	int field_count;
	MYSQL mysql;
	MYSQL_RES *result;
	char array_name[100];
} connection[SQL_COMMANDS];

/*******************************************************************/
/* miscellaneous support routines (error handling etc) */
/*******************************************************************/

void output_error(Tcl_Interp *interp, int sql_number) {
	sprintf(errormsg,"Error %u (%s)",mysql_errno(&connection[sql_number].mysql),mysql_error(&connection[sql_number].mysql));
	Tcl_SetResult(interp,errormsg,TCL_STATIC);
}

/*******************************************************************/
/* sql connect */
/*******************************************************************/

int fbsql_connect(Tcl_Interp *interp, int sql_number, int argc, char **argv) {
	char *host = NULL;
	char *user = NULL;
	char *passwd = NULL;
	char *db = NULL;
	unsigned int port = 0;

	/* check not already connected? */
	if (connection[sql_number].CONNECTED) {
		Tcl_SetResult(interp, "sql connect; already connected.", TCL_STATIC);
		return TCL_ERROR;
	}

	/* updated command line options: fbsql_connect host user password [database] [port] */
	if (argc > 0 && argv[0]) { host = argv[0]; }
	if (argc > 1 && argv[1]) { user = argv[1]; }
	if (argc > 2 && argv[2]) { passwd = argv[2]; }
	if (argc > 3 && argv[3]) { db = argv[3]; }
	if (argc > 4 && argv[4]) { port = atol(argv[4]); }

	mysql_init(&connection[sql_number].mysql);

	if (!(mysql_real_connect(&connection[sql_number].mysql,host,user,passwd,db,port,NULL,0))) {
		output_error(interp,sql_number);
		return TCL_ERROR;
	} else {
		connection[sql_number].CONNECTED = 1;
		return TCL_OK;
	}
}

int fbsql_disconnect(Tcl_Interp *interp, int sql_number) {
	/* check that we are connected? */
	if (!connection[sql_number].CONNECTED) {
		Tcl_SetResult(interp, "sql disconnect; not connected.", TCL_STATIC);
		return TCL_ERROR;
	}

	mysql_close(&connection[sql_number].mysql);
	connection[sql_number].CONNECTED = 0;
	return TCL_OK;
}

/*******************************************************************/
/* sql general commands */
/*******************************************************************/

int fbsql_selectdb(Tcl_Interp *interp, int sql_number, int argc, char **argv) {
	char *database = NULL;

	/* check a database name argument has been specified */
	if (argc <= 0 || argv[0] == NULL) {
		Tcl_SetResult(interp, "sql selectdb database_name; no database name was specified.", TCL_STATIC);
		return TCL_ERROR;
	}

	/* check that we are connected to a mysql server */
	if (!connection[sql_number].CONNECTED) {
		Tcl_SetResult(interp, "sql query statement; you are not connected to a mysql server yet (sql connect).", TCL_STATIC);
		return TCL_ERROR;
	}

	database = argv[0];

	if (mysql_select_db(&connection[sql_number].mysql,database)) {
		output_error(interp,sql_number);
		return TCL_ERROR;
	} else {
		return TCL_OK;
	}
}

int fbsql_numrows(Tcl_Interp *interp, int sql_number, int argc, char **argv) {
	Tcl_Obj *obj_result;

	/* set result object pointer */
	obj_result = Tcl_GetObjResult(interp);
	Tcl_SetIntObj(obj_result,connection[sql_number].NUMROWS);
	
	return TCL_OK;
}

int determine_field_type(int type) {
	/* 0 = char, 1 = numeric, 2 = datetime */
	switch (type) {

	case FIELD_TYPE_BLOB:
	case FIELD_TYPE_NULL:
	case FIELD_TYPE_SET:
	case FIELD_TYPE_STRING:
	case FIELD_TYPE_TIME:
	case FIELD_TYPE_TIMESTAMP:
	case FIELD_TYPE_VAR_STRING:
		return 0;
		break;

	case FIELD_TYPE_DECIMAL:
	case FIELD_TYPE_DOUBLE:
	case FIELD_TYPE_ENUM:
	case FIELD_TYPE_FLOAT:
	case FIELD_TYPE_INT24:
	case FIELD_TYPE_LONG:
	case FIELD_TYPE_LONGLONG:
	case FIELD_TYPE_SHORT:
	case FIELD_TYPE_TINY:
	case FIELD_TYPE_YEAR:
		return 1;
		break;

	case FIELD_TYPE_DATE:
	case FIELD_TYPE_DATETIME:
		return 2;
		break;
	}

	/* return 0 by default, to keep compiler warnings away */
	return 0;
}

/*******************************************************************/
/* sql query command(s) */
/*******************************************************************/

int fbsql_query(Tcl_Interp *interp, int sql_number, int argc, char **argv) {
	char *query = NULL;
	int i, length, field_count;
	MYSQL_RES *result;
	MYSQL_ROW row;
	Tcl_Obj *obj_result;
	Tcl_Obj *obj_row;
	Tcl_Obj *obj_col;
#if UTF_ENCODING
	Tcl_DString ds;
#endif

	if (!connection[sql_number].CONNECTED) {
		Tcl_SetResult(interp, "sql query statement; you are not connected to a mysql server yet (sql connect).", TCL_STATIC);
		return TCL_ERROR;
	}

	/* check there is no other active query? */
	if (connection[sql_number].query_flag) {
		Tcl_SetResult(interp, "Another query cannot be made until the current query has been finished with \"sql endquery\".", TCL_STATIC);
		return TCL_ERROR;
	}

	if (argc <= 0 || argv[0] == NULL) {
		Tcl_SetResult(interp, "sql query statement; no sql query was specified.", TCL_STATIC);
		return TCL_ERROR;
	}

#if UTF_ENCODING
	Tcl_DStringInit(&ds);
	Tcl_UtfToExternalDString(NULL, argv[0], strlen(argv[0]), &ds);
	query = Tcl_DStringValue(&ds);

	/* execute the sql query statement */
	if (mysql_query(&connection[sql_number].mysql,query)) {
		output_error(interp,sql_number);
		Tcl_DStringFree(&ds);
		return TCL_ERROR;
	}
	
	Tcl_DStringFree(&ds);
#else
	query = argv[0];

	/* execute the sql query statement */
	if (mysql_query(&connection[sql_number].mysql,query)) {
		output_error(interp,sql_number);
		return TCL_ERROR;
	}
#endif

	result = mysql_use_result(&connection[sql_number].mysql);
	field_count = mysql_field_count(&connection[sql_number].mysql);

	/* if no results were found and none expected then all ok */
	/* otherwise we assume the query worked ok, now get the results */
	if (result == NULL) {
		if (field_count) {
			output_error(interp,sql_number);
			return TCL_ERROR;
		}
		connection[sql_number].NUMROWS = (int)mysql_affected_rows(&connection[sql_number].mysql);
		return TCL_OK;
	} else {
		/* set result object pointer */
		obj_result = Tcl_GetObjResult(interp);

		/* process all rows from query */
		while ((row = mysql_fetch_row(result)) != NULL) {
			obj_row = Tcl_NewListObj(0,NULL);
			for (i = 0; i < field_count; i++) {
				if (row[i] == NULL) {
					length = 0;
				} else {
					length = strlen(row[i]);
				}
#if UTF_ENCODING
				Tcl_DStringInit(&ds);
				Tcl_ExternalToUtfDString(NULL, row[i], length, &ds);
				obj_col = Tcl_NewStringObj(Tcl_DStringValue(&ds),
						Tcl_DStringLength(&ds));
				Tcl_ListObjAppendElement(interp, obj_row, obj_col);
				Tcl_DStringFree(&ds);
#else
				obj_col = Tcl_NewStringObj(row[i],length);
				Tcl_ListObjAppendElement(interp,obj_row,obj_col);
#endif
			}
			Tcl_ListObjAppendElement(interp,obj_result,obj_row);
		}
		connection[sql_number].NUMROWS = (int)mysql_num_rows(result);
		mysql_free_result(result);
		return TCL_OK;
	}
}

/*******************************************************************/
/* sql query command(s) - non standard enhancements for FastBase */
/*******************************************************************/

int fbsql_startquery(Tcl_Interp *interp, int sql_number, int argc, char **argv) {
	char *query = NULL;
	int i;
	int sql_huge = 0;
#if UTF_ENCODING
	Tcl_DString ds;
#endif

	if (!connection[sql_number].CONNECTED) {
		Tcl_SetResult(interp, "sql startquery statement; you are not connected to a mysql server yet (sql connect).", TCL_STATIC);
		return TCL_ERROR;
	}

	/* check there is no other active query? */
	if (connection[sql_number].query_flag) {
		Tcl_SetResult(interp, "Another query cannot be made until the current query has been finished with \"sql endquery\".", TCL_STATIC);
		return TCL_ERROR;
	}

	if (argc <= 0 || argv[0] == NULL) {
		Tcl_SetResult(interp, "sql startquery statement; no sql query was specified.", TCL_STATIC);
		return TCL_ERROR;
	}

	connection[sql_number].use_array = 0;

	/* process additional "command-line" options */
	for (i = 1; i < argc; i++) {
		if (argv[i] != NULL) {
			if (strcmp(argv[i],"-huge") == 0) {
				sql_huge = 1;
			} else if (strcmp(argv[i],"-array") == 0) {
				if (argc <= i || argv[i + 1] == NULL) {
					Tcl_SetResult(interp, "sql startquery; no array name specified with -array option.", TCL_STATIC);
					return TCL_ERROR;
				}
				connection[sql_number].use_array = 1;
				strcpy(connection[sql_number].array_name,argv[i+1]);
				i++;
			} else {
				Tcl_SetResult(interp, "sql startquery; invalid option on command line.", TCL_STATIC);
				return TCL_ERROR;
			}
		}
	}

#if UTF_ENCODING
	Tcl_DStringInit(&ds);
	Tcl_UtfToExternalDString(NULL, argv[0], strlen(argv[0]), &ds);
	query = Tcl_DStringValue(&ds);
#else
	query = argv[0];
#endif

	/* execute the sql query statement */
	if (mysql_query(&connection[sql_number].mysql,query)) {
		output_error(interp,sql_number);
#if UTF_ENCODING
		Tcl_DStringFree(&ds);
#endif
		return TCL_ERROR;
	}

#if UTF_ENCODING
	Tcl_DStringFree(&ds);
#endif

	/* use or store the result? */
	/* the default is to store it locally */
	/* for large queries use "-huge" option */
	if (sql_huge) {
		connection[sql_number].result = mysql_use_result(&connection[sql_number].mysql);
	} else {
		connection[sql_number].result = mysql_store_result(&connection[sql_number].mysql);
		connection[sql_number].NUMROWS = (int)mysql_num_rows(connection[sql_number].result);
	}
	connection[sql_number].field_count = mysql_field_count(&connection[sql_number].mysql);

	/* if no results were found and none expected then all ok */
	/* otherwise we assume the query worked ok, now get the results */
	if (connection[sql_number].result == NULL) {
		if (connection[sql_number].field_count) {
			output_error(interp,sql_number);
			return TCL_ERROR;
		}
		Tcl_SetResult(interp, "sql startquery; query executed ok but returned no results.", TCL_STATIC);
		return TCL_ERROR;
	}

	/* return all ok now, see fetchrow & endquery for more details */
	connection[sql_number].query_flag = 1;
	return TCL_OK;
}

int fbsql_fetchrow(Tcl_Interp *interp, int sql_number, int argc, char **argv) {
	int i, field_type, length;
	MYSQL_ROW row;
	MYSQL_FIELD *field;
	Tcl_Obj *obj_result;
	Tcl_Obj *obj_col;
	Tcl_Obj *obj_array;
	Tcl_Obj *obj_element;
#if UTF_ENCODING
	Tcl_DString ds;
#endif

	/* check we are connected? */
	if (!connection[sql_number].CONNECTED) {
		Tcl_SetResult(interp, "Not connected to a server.", TCL_STATIC);
		return TCL_ERROR;
	}
	/* check we had a query started? */
	if (!connection[sql_number].query_flag) {
		Tcl_SetResult(interp, "No query has been started.", TCL_STATIC);
		return TCL_ERROR;
	}

	/* fetch some data? */
	row = mysql_fetch_row(connection[sql_number].result);

	if (row == NULL) {
		/* if we come to the end of the data return nothing */
		/* if using an array set the array elements to default/NULL values */
		if (connection[sql_number].use_array) {
			obj_array = Tcl_NewStringObj(connection[sql_number].array_name,strlen(connection[sql_number].array_name));

			for (i = 0; i < connection[sql_number].field_count; i++) {
				/* get field information */
				field = mysql_fetch_field_direct(connection[sql_number].result,i);
				if (field == NULL) continue;

				obj_element = Tcl_NewStringObj(field->name,strlen(field->name));

				/* 0 = char, 1 = numeric, 2 = datetime */
				field_type = determine_field_type(field->type);
				/* if the type is numeric then store 0.00 as the result */
				if (field_type == 1) {
					obj_col = Tcl_NewDoubleObj(0);
				} else {
					obj_col = Tcl_NewStringObj(NULL,0);
				}

				Tcl_ObjSetVar2(interp,obj_array,obj_element,obj_col,0);
				/* we no longer have any use for the element object */
				Tcl_DecrRefCount(obj_element);
			}
		/* we no longer have any use for the array object */
		Tcl_DecrRefCount(obj_array);
		}
		return TCL_OK;
	}

	/* if an array name was specified on the sql command line then we */
	/* transfer all results to that array - special FastBase conversions */
	/* otherwise return the row as a list of columns */
	if (connection[sql_number].use_array) {
		obj_array = Tcl_NewStringObj(connection[sql_number].array_name,strlen(connection[sql_number].array_name));

		for (i = 0; i < connection[sql_number].field_count; i++) {
			/* get field information */
			field = mysql_fetch_field_direct(connection[sql_number].result,i);
			if (field == NULL) continue;

			obj_element = Tcl_NewStringObj(field->name,strlen(field->name));

			/* 0 = char, 1 = numeric, 2 = datetime */
			field_type = determine_field_type(field->type);

			if (row[i] == NULL) {
				/* if the field is NULL and the type is numeric then store 0.00 as the result */
				if (field_type == 1) {
					obj_col = Tcl_NewDoubleObj(0);
				} else {
					obj_col = Tcl_NewStringObj(NULL,0);
				}
			} else {
				/* if date field check for the value "0000-00-00" */
				/* replace this with NULL */
				if (field_type == 2 && strlen(row[i]) >= 10) {
					if (strncmp(row[i],"0000-00-00",10) == 0) {
						obj_col = Tcl_NewStringObj(NULL,0);
					} else {
						obj_col = Tcl_NewStringObj(row[i],strlen(row[i]));
					}
				} else {
#if UTF_ENCODING
					Tcl_DStringInit(&ds);
					Tcl_ExternalToUtfDString(NULL, row[i], strlen(row[i]), &ds);
					obj_col = Tcl_NewStringObj(Tcl_DStringValue(&ds),
							Tcl_DStringLength(&ds));
					Tcl_DStringFree(&ds);
#else
					obj_col = Tcl_NewStringObj(row[i],strlen(row[i]));
#endif
				}
			}

			Tcl_ObjSetVar2(interp,obj_array,obj_element,obj_col,0);
			/* we no longer have any use for the element object */
			Tcl_DecrRefCount(obj_element);
		}
		/* we no longer have any use for the array object */
		Tcl_DecrRefCount(obj_array);
		/* set result object pointer */
		obj_result = Tcl_GetObjResult(interp);
		/* return result = array name */
		Tcl_SetStringObj(obj_result,connection[sql_number].array_name,strlen(connection[sql_number].array_name));
		return TCL_OK;
	} else {
		/* set result object pointer */
		obj_result = Tcl_GetObjResult(interp);
		/* process all columns from query row */
		for (i = 0; i < connection[sql_number].field_count; i++) {
			if (row[i] == NULL) {
				length = 0;
			} else {
				length = strlen(row[i]);
			}
#if UTF_ENCODING
			Tcl_DStringInit(&ds);
			Tcl_ExternalToUtfDString(NULL, row[i], length, &ds);
			obj_col = Tcl_NewStringObj(Tcl_DStringValue(&ds),
					Tcl_DStringLength(&ds));
			Tcl_ListObjAppendElement(interp, obj_result, obj_col);
			Tcl_DStringFree(&ds);
#else
			obj_col = Tcl_NewStringObj(row[i],length);
			Tcl_ListObjAppendElement(interp,obj_result,obj_col);
#endif
		}
		return TCL_OK;
	}
}

int fbsql_endquery(Tcl_Interp *interp, int sql_number, int argc, char **argv) {
	/* check we are connected? */
	if (!connection[sql_number].CONNECTED) {
		Tcl_SetResult(interp, "Not connected to a server.", TCL_STATIC);
		return TCL_ERROR;
	}
	/* check we had a query started? */
	if (!connection[sql_number].query_flag) {
		Tcl_SetResult(interp, "No query has been started.", TCL_STATIC);
		return TCL_ERROR;
	}

	connection[sql_number].NUMROWS = (int)mysql_num_rows(connection[sql_number].result);
	mysql_free_result(connection[sql_number].result);
	connection[sql_number].query_flag = 0;
	return TCL_OK;
}

/*******************************************************************/
/* tcl sql command */
/*******************************************************************/

int SqlCmd (ClientData sql_word, Tcl_Interp *interp, int argc, char **argv) {
	int sql_number = (int)sql_word;

	/* any command specified */
	if (argc <= 1) {
		Tcl_SetResult(interp, "Usage: sql connect|selectdb|query|numrows|disconnect|version; please try again.", TCL_STATIC);
		return TCL_ERROR;
	}

	/* check command list */
	if (strcmp(argv[1],"connect") == 0) {
		return fbsql_connect(interp,sql_number,argc-2,argv+2);
	} else if (strcmp(argv[1],"selectdb") == 0) {
		return fbsql_selectdb(interp,sql_number,argc-2,argv+2);
	} else if (strcmp(argv[1],"query") == 0) {
		return fbsql_query(interp,sql_number,argc-2,argv+2);
	} else if (strcmp(argv[1],"numrows") == 0) {
		return fbsql_numrows(interp,sql_number,argc-2,argv+2);
	} else if (strcmp(argv[1],"disconnect") == 0) {
		return fbsql_disconnect(interp,sql_number);
	} else if (strcmp(argv[1],"version") == 0) {
		Tcl_SetResult(interp, "FastBase MySQL Interface for Tcl; version 1.06", TCL_STATIC);
		return TCL_OK;
	} else if (strcmp(argv[1],"startquery") == 0) {
		return fbsql_startquery(interp,sql_number,argc-2,argv+2);
	} else if (strcmp(argv[1],"fetchrow") == 0) {
		return fbsql_fetchrow(interp,sql_number,argc-2,argv+2);
	} else if (strcmp(argv[1],"endquery") == 0) {
		return fbsql_endquery(interp,sql_number,argc-2,argv+2);
	} else {
		/* assume query command shortform: sql STATEMENT */
		return fbsql_query(interp,sql_number,argc-1,argv+1);
	}
}

/*******************************************************************/
/* tcl fbwait command */
/*******************************************************************/

static char *		WaitVariableProc _ANSI_ARGS_((ClientData clientData,
			    Tcl_Interp *interp, char *name1, char *name2,
			    int flags));

static char *
WaitVariableProc(clientData, interp, name1, name2, flags)
ClientData clientData;	/* Pointer to integer to set to 1. */
Tcl_Interp *interp;		/* Interpreter containing variable. */
char *name1;		/* Name of variable. */
char *name2;		/* Second part of variable name. */
int flags;			/* Information about what happened. */
{
	done = 1;
	return (char *) NULL;
}

int fbWaitCmd(clientData, interp, argc, argv)
ClientData clientData;					/* Main window associated with interpreter. */
Tcl_Interp *interp;					/* Current interpreter. */
int argc;					/* Number of arguments. */
char **argv;					/* Argument strings. */
{
	if (argc != 2) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
			argv[0], " variable_name\" (based on tkwait var name)", (char *) NULL);
		return TCL_ERROR;
	}

	if (Tcl_TraceVar(interp, argv[1],
		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
		WaitVariableProc,
		(ClientData) 0) != TCL_OK) {
		return TCL_ERROR;
	}

	done = 0;
	while (!done) {
		/* process file and timer events only, ie: exclude window and idle */
		Tcl_DoOneEvent(TCL_FILE_EVENTS | TCL_TIMER_EVENTS);
	}

	Tcl_UntraceVar(interp, argv[1],
		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
		WaitVariableProc,
		(ClientData) 0);

	/* Clear out the interpreter's result, since it may have been set by event handlers */
	Tcl_ResetResult(interp);

	return TCL_OK;
}

/*******************************************************************/
/* define the TCL initialisation code to create the "fbsql" command */
/*******************************************************************/

/* haven't tested this yet (for building on Unix) */
#ifdef WINDOWS
extern __declspec(dllexport) int Fbsql_Init(Tcl_Interp *interp);
#else
extern int Fbsql_Init(Tcl_Interp *interp);
#endif

int Fbsql_Init(Tcl_Interp *interp) {
	int i;
	char command_name[10];

	/* stubs? */
	if (Tcl_InitStubs(interp, "8.1", 0) == NULL) {
		return TCL_ERROR;
	}

	/* create the main "sql" command */
	Tcl_CreateCommand (interp, "sql", SqlCmd, (ClientData) 0, (Tcl_CmdDeleteProc*) NULL);

	/* create new "fbwait" command */
	Tcl_CreateCommand (interp, "fbwait", fbWaitCmd, (ClientData) 0, (Tcl_CmdDeleteProc*) NULL);

	/* initialise the connection structure */
	for (i = 0; i < SQL_COMMANDS; i++) {
		connection[i].CONNECTED = 0;
		connection[i].query_flag = 0;
		if (i > 0) {
			/* create the extra commands "sql1, sql2, sql3, sql4, sql5" */
			/* these commands are the same as "sql" but use separate mysql connections */
			sprintf(command_name,"sql%d",i);
			Tcl_CreateCommand (interp, command_name, SqlCmd, (ClientData) i, (Tcl_CmdDeleteProc*) NULL);
		}
	}

	return (Tcl_PkgProvide(interp,"Fbsql","1.06") == TCL_ERROR ? TCL_ERROR : TCL_OK);
}
