#ifndef lint
static char rcsid [] = "@(#$Id$ (C) 1998 CDKIT";
#endif
/*
*
* MYSQL interface to Tcl
* This file was adapted by J.F. Dockes (dockes@cdkit.remcomp.fr) from
* the msqltcl package by:
* Hakan Soderstrom, hs@soderstrom.se
*
*/
/*
* Copyright (c) 1994, 1995 Hakan Soderstrom and Tom Poindexter
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without fee,
* provided that the above copyright notice and this permission notice
* appear in all copies of the software and related documentation.
*
* THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
* EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
* WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL HAKAN SODERSTROM OR SODERSTROM PROGRAMVARUVERKSTAD
* AB BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL
* DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF THE POSSIBILITY
* OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <tcl.h>
#include "mysql.h"
#include "errmsg.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#ifdef HASNO_GETHOSTNAME_PROTO
extern int gethostname(char *name, size_t namelen);
#endif
// Version is only defined or used when compiled as part of the sqlscreen
// package
#ifndef VERSION
#define VERSION "3.0"
#endif
#define MYSQL_HANDLES 50 /* Max number of query handles. */
#define MYSQL_CONS 15 /* Max number db connections. */
#define MYSQL_BUFF_SIZE 1024 /* Conversion buffer size for various needs. */
#define MYSQL_SMALL_SIZE TCL_RESULT_SIZE /* Smaller buffer size. */
#define MYSQL_NAME_LEN 80 /* Max. host, database name length. */
/*
* Connection/Query handle.
*
* As we do not use mysql_use_result(), the mysql connections
* keep no state between tcl-level calls.
*
* So the MySQL connections to a database can be shared/multiplexed
* between the user-level handles.
*
* We use an array for mysql connections and an array for TCL handles,
* and many TCL handles can share the same db connection.
* We do not try to share connections to different databases as I don't know
* the cost of changing the db on a connection (else we might just use
* exactly one MySQL connection per host)
*
* The current code does not manage connections through different
* users/passwds to the same database (this could quite easily be added).
*
*/
#define freeZ(P) {if ((P)) {free((P));(P) = 0;}}
/* The shared MySQL connection structure */
typedef struct MysqlCon {
MYSQL connecbuf; /* Storage for the MySQL connection handle */
char *host; /* Host name, if connected, else "". */
char *database; /* Db name, if selected, else "" */
char *user;
char *passwd;
int port;
int use_count;
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2
Tcl_Encoding tclenc;
#endif
} MysqlCon;
static MysqlCon mysqlCons[MYSQL_CONS];
/* The user level structure. Keeps a pointer to the actual connection
* and the query result. (When appropriate). */
typedef struct MysqlTclHandle {
MysqlCon *connection ; /* not NULL if connected */
MYSQL_RES* result; /* Stored result, if any, else NULL */
int res_count; /* Count of unfetched rows in result. */
int col_count; /* Column count in result, if any. */
int last_insert_id; /* After a mysql_exec */
#ifdef HANDLE_DEBUG
char *stmt; /* Remember statement: for debug */
#endif
} MysqlTclHandle;
static MysqlTclHandle mysqlHandle[MYSQL_HANDLES];
#define MYSQLCON(hand) (&mysqlHandle[hand].connection->connecbuf)
/* Prefix string used to identify handles.
* MYSQL_HPREFIX_LEN must be strlen(MysqlHandlePrefix). */
static char *MysqlHandlePrefix = "mysql";
#define MYSQL_HPREFIX_LEN 5
/* TCL array for status info, and its elements. */
static char *MysqlStatusArr = "mysqlstatus";
#define MYSQL_STATUS_CODE "code"
#define MYSQL_STATUS_CMD "command"
#define MYSQL_STATUS_MSG "message"
#define MYSQL_STATUS_NULLV "nullvalue"
/* C variable corresponding to mysqlstatus(nullvalue) */
static char* MysqlNullvalue = NULL ;
#define MYSQL_NULLV_INIT ""
/* Options to the 'info', 'result', 'col' combo commands. */
static char* MysqlDbOpt[] =
{
"dbname", "dbname?", "tables", "host", "host?", "databases"
};
#define MYSQL_INFNAME_OPT 0
#define MYSQL_INFNAMEQ_OPT 1
#define MYSQL_INFTABLES_OPT 2
#define MYSQL_INFHOST_OPT 3
#define MYSQL_INFHOSTQ_OPT 4
#define MYSQL_INFLIST_OPT 5
#define MYSQL_INF_OPT_MAX 5
static char* MysqlResultOpt[] =
{
"rows", "rows?", "cols", "cols?", "current", "current?"
};
#define MYSQL_RESROWS_OPT 0
#define MYSQL_RESROWSQ_OPT 1
#define MYSQL_RESCOLS_OPT 2
#define MYSQL_RESCOLSQ_OPT 3
#define MYSQL_RESCUR_OPT 4
#define MYSQL_RESCURQ_OPT 5
#define MYSQL_RES_OPT_MAX 5
/* Column info definitions. */
static char* MysqlColkey[] =
{
"table", "name", "type", "length", "prim_key", "non_null"
};
#define MYSQL_COL_TABLE_K 0
#define MYSQL_COL_NAME_K 1
#define MYSQL_COL_TYPE_K 2
#define MYSQL_COL_LENGTH_K 3
#define MYSQL_COL_PRIMKEY_K 4
#define MYSQL_COL_NONNULL_K 5
#define MYSQL_COL_K_MAX 5
/* tcl8.4 uses const (CONST84) argvs to call cmdprocs. Stay compatible */
#ifndef CONST84
#define CONST84
#endif
/* Prototypes for all functions. */
extern Tcl_CmdProc Mysqltcl_Connect;
extern Tcl_CmdProc Mysqltcl_Use;
extern Tcl_CmdProc Mysqltcl_Sel;
extern Tcl_CmdProc Mysqltcl_Next;
extern Tcl_CmdProc Mysqltcl_Seek;
extern Tcl_CmdProc Mysqltcl_Map;
extern Tcl_CmdProc Mysqltcl_Exec;
extern Tcl_CmdProc Mysqltcl_Close;
extern Tcl_CmdProc Mysqltcl_Info;
extern Tcl_CmdProc Mysqltcl_Result;
extern Tcl_CmdProc Mysqltcl_Col;
extern Tcl_CmdProc Mysqltcl_State;
extern Tcl_CmdProc Mysqltcl_InsertId;
/* CONFLICT HANDLING
*
* Every command begins by calling 'mysql_prologue'.
* This function resets mysqlstatus(code) to zero; the other array elements
* retain their previous values.
* The function also saves argc/argv in global variables.
* After this the command processing proper begins.
*
* If there is a conflict, the message is taken from one of the following
* sources,
* -- this code (mysql_prim_confl),
* -- the database server (mysql_server_confl),
* A complete message is put together from the above plus the name of the
* command where the conflict was detected.
* The complete message is returned as the Tcl result and is also stored in
* mysqlstatus(message).
* mysqlstatus(code) is set to "-1".
* In addition, the whole command where the conflict was detected is put
* together from the saved argc/argv and is copied into mysqlstatus(command).
*/
static Tcl_Interp* saved_interp;
static int saved_argc;
static CONST84 char** saved_argv;
/* strcmp handling null pointers */
static int nllstrcmp(const char *s1, const char *s2)
{
if (s1 == 0 && s2 == 0)
return 0;
if (s1 == 0 && s2 != 0)
return -1;
if (s1 != 0 && s2 == 0)
return 1;
return strcmp(s1, s2);
}
#ifdef HANDLE_DEBUG
/* Debug: dump the handle array state */
static void dump_handles(FILE *fp)
{
int i; MysqlCon *c; MysqlTclHandle *h;
fprintf(fp, "\n\n");
for (i = 0, h = mysqlHandle; i < MYSQL_HANDLES;i++, h++) {
fprintf(fp, "Handle %d: ", i);
if (h->connection)
fprintf(fp, " Connection: %d ", h->connection - mysqlCons);
else fprintf(fp, " Not connected ");
if (h->result) fprintf(fp, " Result stored: cnt %d lastid %d\n",
h->col_count, h->last_insert_id);
else fprintf(fp, " No result\n");
if (h->stmt) fprintf(fp, " Last statement: '%s'\n", h->stmt);
}
for (i = 0, c = mysqlCons; i < MYSQL_CONS; i++, c++) {
fprintf(fp, "conn %d: host %s database %s user %s port %d use_c %d\n",
i, c->host, c->database, c->user, c->port, c->use_count);
}
}
#endif /* HANDLE_DEBUG */
/* Initialize fields in a connection structure. Doesn't deal with the MySQL
handle proper */
static void init_con(MysqlCon *con)
{
freeZ(con->host);
freeZ(con->database);
freeZ(con->user);
freeZ(con->passwd);
con->port = 0;
con->use_count = 0;
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2
if (con->tclenc)
Tcl_FreeEncoding(con->tclenc);
con->tclenc = 0;
#endif
}
/* Note: doesn't (must not) reset old values */
static void set_con(MysqlCon *con, const char *host, const char *database,
const char *user, const char *passwd, int port)
{
if (host) {
freeZ(con->host);
con->host = strdup(host);
}
if (database) {
freeZ(con->database);
con->database = strdup(database);
}
if (user) {
freeZ(con->user);
con->user = strdup(user);
}
if (passwd) {
freeZ(con->passwd);
con->passwd = strdup(passwd);
}
if (port != 0)
con->port = port;
}
/* Use/release an entry from the connections array */
static void
release_con(MysqlCon *con)
{
if (con == 0)
return;
if (con->use_count <= 0) {
con->use_count = 0;
return;
}
con->use_count--;
if (con->use_count == 0) {
mysql_close(&con->connecbuf);
init_con(con);
}
}
static void
use_con(MysqlTclHandle *hand, MysqlCon *con)
{
hand->connection = con;
con->use_count++;
}
#if MYSQL_VERSION_ID < 32200
extern unsigned int mysql_port;
#endif
/* Try reconnecting a timed out handle */
static int
try_recon(MysqlCon *con)
{
int myerrno = mysql_errno(&con->connecbuf);
switch (myerrno) {
case CR_SERVER_GONE_ERROR:
case CR_SERVER_LOST:
break;
default:
return -1;
}
mysql_close(&con->connecbuf);
#if MYSQL_VERSION_ID >= 32200
mysql_init(&con->connecbuf);
if (mysql_real_connect(&con->connecbuf, con->host, con->user,
con->passwd, con->database, con->port, 0, 0) == 0)
return -1;
#else
if (con->port)
mysql_port = con->port;
if (mysql_connect(&con->connecbuf, con->host, con->user, con->passwd) == 0)
return -1;
if (con->database && mysql_select_db(&con->connecbuf, con->database) != 0)
return -1;
#endif
return 0;
}
/*
*----------------------------------------------------------------------
* mysql_reassemble
* Reassembles the current command from the saved argv; copies it into
* mysqlstatus(command).
*/
static void
mysqlReassemble ()
{
unsigned int flags = TCL_GLOBAL_ONLY | TCL_LIST_ELEMENT;
int idx ;
for (idx = 0; idx < saved_argc; ++idx)
{
Tcl_SetVar2 (saved_interp, MysqlStatusArr, MYSQL_STATUS_CMD,
saved_argv[idx], flags) ;
flags |= TCL_APPEND_VALUE ;
}
}
/*
*----------------------------------------------------------------------
* mysqlPrimConfl
* Conflict handling after a primitive conflict.
*
*/
static int
mysqlPrimConfl (char* msg)
{
const char *resultPtr;
Tcl_SetVar2 (saved_interp, MysqlStatusArr, MYSQL_STATUS_CODE, "-1",
TCL_GLOBAL_ONLY);
Tcl_SetResult (saved_interp, "", TCL_STATIC) ;
Tcl_AppendResult (saved_interp, saved_argv[0], ": ", msg, (char*)NULL) ;
resultPtr = Tcl_GetStringResult(saved_interp);
Tcl_SetVar2 (saved_interp, MysqlStatusArr, MYSQL_STATUS_MSG,
resultPtr, TCL_GLOBAL_ONLY);
mysqlReassemble () ;
return TCL_ERROR ;
}
/*
*----------------------------------------------------------------------
* mysqlServerConfl
* Conflict handling after an mySQL conflict.
*
*/
static int
mysqlServerConfl (MYSQL *sock)
{
const char *cp;
const char *resultPtr;
Tcl_SetVar2 (saved_interp, MysqlStatusArr, MYSQL_STATUS_CODE, "-1",
TCL_GLOBAL_ONLY);
Tcl_SetResult (saved_interp, "", TCL_STATIC) ;
if (sock)
cp = mysql_error(sock);
else cp = NULL;
Tcl_AppendResult (saved_interp, saved_argv[0], "/db server: ",
(cp == NULL) ? "" : cp, (char*)NULL) ;
resultPtr = Tcl_GetStringResult(saved_interp);
Tcl_SetVar2 (saved_interp, MysqlStatusArr, MYSQL_STATUS_MSG,
resultPtr, TCL_GLOBAL_ONLY);
mysqlReassemble () ;
return TCL_ERROR ;
}
/*----------------------------------------------------------------------
* get_handle_plain
* Check handle syntax (and nothing else).
* RETURN: mysqlHandle index number or -1 on error.
*/
static int
get_handle_plain (CONST84 char *handle)
{
int hi ;
CONST84 char *hp = handle ;
if (strncmp(handle, MysqlHandlePrefix, MYSQL_HPREFIX_LEN) != 0)
goto wrong;
hp += MYSQL_HPREFIX_LEN;
for (; *hp; hp++)
if (!isdigit((int)(*hp)))
goto wrong;
hi = atoi(handle + MYSQL_HPREFIX_LEN);
if (hi >= 0 && hi < MYSQL_HANDLES)
return hi;
wrong:
mysqlPrimConfl ("weird handle");
return -1;
}
/*----------------------------------------------------------------------
* get_handle_conn
* Check handle syntax, verify that the handle is connected.
* RETURN: mysqlHandle index number or -1 on error.
*/
static int
get_handle_conn (CONST84 char *handle)
{
int hi ;
if ((hi = get_handle_plain(handle)) < 0)
return -1 ;
if (mysqlHandle[hi].connection == NULL) {
mysqlPrimConfl ("handle not connected") ;
return -1 ;
}
return hi ;
}
/*----------------------------------------------------------------------
* get_handle_db
* Check handle syntax, verify that the handle is connected and that
* there is a current database.
* RETURN: MysqlHandle index number or -1 on error.
*/
static int
get_handle_db (CONST84 char *handle)
{
int hi ;
if ((hi = get_handle_conn(handle)) < 0)
return -1;
if (mysqlHandle[hi].connection->database == 0) {
mysqlPrimConfl ("no current database") ;
return -1 ;
}
return hi;
}
/*----------------------------------------------------------------------
* get_handle_res
* Check handle syntax, verify that the handle is connected and that
* there is a current database and that there is a pending result.
* RETURN: MysqlHandle index number or -1 on error.
*/
static int
get_handle_res (CONST84 char *handle)
{
int hi ;
if ((hi = get_handle_db(handle)) < 0)
return -1;
if (mysqlHandle[hi].result == NULL) {
mysqlPrimConfl ("no result pending") ; /* */
return -1 ;
} else
return hi ;
}
/*
*----------------------------------------------------------------------
* handle_init
* Initialize the handle and connection arrays.
*/
static void
handle_init ()
{
int i;
for (i = 0; i < MYSQL_HANDLES; i++) {
memset(&mysqlHandle[i], 0, sizeof(MysqlTclHandle));
}
for (i = 0; i < MYSQL_CONS; i++) {
memset(&mysqlCons[i], 0, sizeof(MysqlCon));
}
}
/*
*----------------------------------------------------------------------
* clear_msg
*
* Clears all error and message elements in the global array variable.
*
*/
static void
clear_msg(Tcl_Interp *interp)
{
Tcl_SetVar2(interp, MysqlStatusArr, MYSQL_STATUS_CODE, "0", TCL_GLOBAL_ONLY);
Tcl_SetVar2(interp, MysqlStatusArr, MYSQL_STATUS_CMD, "", TCL_GLOBAL_ONLY);
Tcl_SetVar2(interp, MysqlStatusArr, MYSQL_STATUS_MSG, "", TCL_GLOBAL_ONLY);
}
/*
*----------------------------------------------------------------------
* mysqlPrologue
*
* Does most of standard command prologue; required for all commands
* having conflict handling.
* 'req_args' must be the required number of arguments for the command,
* including the command word.
* 'usage_msg' must be a usage message, leaving out the command name.
* Checks the handle assumed to be present in argv[1] if 'check' is not NULL.
* RETURNS: Handle index or -1 on failure.
* Returns zero if 'check' is NULL.
* SIDE EFFECT: Sets the Tcl result on failure.
*/
static int
mysqlPrologue (
Tcl_Interp *interp,
int argc,
CONST84 char **argv,
int req_args,
int (*check) (CONST84 char *), /* Pointer to function for checking the handle. */
char *usage_msg)
{
char buf[MYSQL_BUFF_SIZE];
int hand = 0;
int need;
/* Reset mysqlstatus(code). */
Tcl_SetVar2 (interp, MysqlStatusArr, MYSQL_STATUS_CODE, "0",
TCL_GLOBAL_ONLY);
/* Save command environment. */
saved_interp = interp;
saved_argc = argc ;
saved_argv = argv ;
/* Check number of minimum args. */
if ((need = req_args - argc) > 0)
{
sprintf (buf, "%d more %s needed: %s %s", need, (need>1)?"args":"arg",
argv[0], usage_msg);
(void)mysqlPrimConfl (buf) ;
return -1 ;
}
/* Check the handle.
* The function is assumed to set the status array on conflict.
*/
if (check != NULL && (hand = check (argv[1])) < 0)
return -1 ;
return hand;
}
/*
*----------------------------------------------------------------------
* mysqlColinfo
*
* Given an MYSQL_FIELD struct and a string keyword appends a piece of
* column info (one item) to the Tcl result.
* ASSUMES 'fld' is non-null.
* RETURNS 0 on success, 1 otherwise.
* SIDE EFFECT: Sets the result and status on failure.
*/
static int
mysqlColinfo (Tcl_Interp *interp,
MYSQL_FIELD* fld,
CONST84 char* keyw)
{
char buf[MYSQL_SMALL_SIZE];
char keybuf[MYSQL_SMALL_SIZE];
int idx ;
char* res ;
int retcode ;
for (idx = 0;
idx <= MYSQL_COL_K_MAX && strcmp (MysqlColkey[idx], keyw) != 0;
idx++) ;
switch (idx)
{
case MYSQL_COL_TABLE_K:
res = fld->table ;
break ;
case MYSQL_COL_NAME_K:
res = fld->name ;
break ;
case MYSQL_COL_TYPE_K:
switch (fld->type)
{
case FIELD_TYPE_DECIMAL: res = "decimal";break;
case FIELD_TYPE_CHAR: res = "char";break;
case FIELD_TYPE_SHORT: res = "short";break;
case FIELD_TYPE_LONG: res = "long";break;
case FIELD_TYPE_FLOAT: res = "float";break;
case FIELD_TYPE_DOUBLE: res = "double";break;
case FIELD_TYPE_NULL: res = "null";break;
case FIELD_TYPE_TIMESTAMP: res = "timestamp";break;
case FIELD_TYPE_LONGLONG: res = "longlong";break;
case FIELD_TYPE_INT24: res = "int24";break;
case FIELD_TYPE_DATE: res = "date";break;
case FIELD_TYPE_TIME: res = "time";break;
case FIELD_TYPE_DATETIME: res = "datetime";break;
case FIELD_TYPE_TINY_BLOB:res = "tiny_blob";break;
case FIELD_TYPE_MEDIUM_BLOB:res = "medium_blob";break;
case FIELD_TYPE_LONG_BLOB:res = "long_blob";break;
case FIELD_TYPE_BLOB: res = "blob";break;
case FIELD_TYPE_VAR_STRING:res = "var_string";break;
case FIELD_TYPE_STRING: res = "string";break;
default:
sprintf (buf, "column '%s' has weird datatype %d", fld->name,
(int)fld->type) ;
res = NULL ;
}
break ;
case MYSQL_COL_LENGTH_K:
sprintf (buf, "%lu", fld->length) ;
res = buf ;
break ;
#ifdef IS_PRI_KEY
case MYSQL_COL_PRIMKEY_K:
sprintf (buf, "%c", (IS_PRI_KEY(fld->flags))?'1':'0') ;
res = buf ;
break ;
#endif
case MYSQL_COL_NONNULL_K:
sprintf (buf, "%c", (IS_NOT_NULL(fld->flags))?'1':'0') ;
res = buf ;
break ;
default:
if (strlen (keyw) >= MYSQL_NAME_LEN)
{
strncpy (keybuf, keyw, MYSQL_NAME_LEN) ;
strcat (keybuf, "...") ;
}
else
strcpy (keybuf, keyw) ;
sprintf (buf, "unknown option: %s", keybuf) ;
res = NULL ;
}
if (res == NULL)
{
(void)mysqlPrimConfl (buf) ;
retcode = 1 ;
}
else
{
Tcl_AppendElement (interp, res) ;
retcode = 0 ;
}
return retcode ;
}
/*
*----------------------------------------------------------------------
* Mysqltcl_Kill
* Close all connections.
*
*/
void
Mysqltcl_Kill (void * client_data)
{
int i ;
for (i = 0; i < MYSQL_CONS; i++) {
if (mysqlCons[i].use_count > 0)
mysql_close(&mysqlCons[i].connecbuf);
init_con(mysqlCons + i);
}
for (i = 0; i < MYSQL_HANDLES; i++) {
if (mysqlHandle[i].result)
mysql_free_result(mysqlHandle[i].result) ;
#ifdef HANDLE_DEBUG
freeZ(mysqlHandle[i].stmt);
#endif
}
/* Resets the whole arrays to 0, doesn't deal with any deallocation */
handle_init();
}
/*
*----------------------------------------------------------------------
* Mysqltcl_Init
* Perform all initialization for the MYSQL to Tcl interface.
* Adds additional commands to interp, creates message array, initializes
* all handles.
*
* A call to Mysqltcl_Init should exist in Tcl_CreateInterp or
* Tcl_CreateExtendedInterp.
*/
int
Mysqltcl_Init (Tcl_Interp *interp)
{
char nbuf[MYSQL_SMALL_SIZE];
/*
* Initialize mySQL proc structures
*/
handle_init () ;
/*
* Initialize the new Tcl commands.
* Deleting any command will close all connections.
*/
Tcl_CreateCommand (interp,"mysqlconnect", Mysqltcl_Connect, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqluse", Mysqltcl_Use, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlsel", Mysqltcl_Sel, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlnext", Mysqltcl_Next, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlseek", Mysqltcl_Seek, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlmap", Mysqltcl_Map, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlexec", Mysqltcl_Exec, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlclose", Mysqltcl_Close, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlinfo", Mysqltcl_Info, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlresult", Mysqltcl_Result, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlcol", Mysqltcl_Col, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp, "mysqlstate", Mysqltcl_State, (ClientData)NULL,
Mysqltcl_Kill);
Tcl_CreateCommand (interp,"mysqlinsertid",Mysqltcl_InsertId,(ClientData)NULL,
Mysqltcl_Kill);
/* Initialize mysqlstatus global array. */
clear_msg(interp);
/* Link the null value element to the corresponding C variable. */
if ((MysqlNullvalue = (char*)malloc (12)) == NULL) {
fprintf (stderr, "*** mysqltcl: out of memory\n") ;
return TCL_ERROR ;
}
(void)strcpy (MysqlNullvalue, MYSQL_NULLV_INIT);
(void)sprintf (nbuf, "%s(%s)", MysqlStatusArr, MYSQL_STATUS_NULLV) ;
Tcl_LinkVar (interp, nbuf, (char*)&MysqlNullvalue, TCL_LINK_STRING) ;
Tcl_PkgProvide(interp, "mysqltcl", VERSION);
return TCL_OK;
}
/* Try to allocate an unused connection structure and connect it */
static MysqlCon *
tryallocconnect(const char *host, const char *user, const char *passwd,
int port)
{
int connectries;
int i;
MysqlCon *connect = 0;
for (i = 0; i < MYSQL_CONS; i++)
if (mysqlCons[i].use_count <= 0)
break;
if (i == MYSQL_CONS) {
mysqlPrimConfl( "No free connection slots!");
return 0;
}
init_con(mysqlCons + i);
/* We always retry connections because of the bad handshake stuff */
for (connectries = 0; connectries < 4; connectries++) {
if (connectries != 0) {
sleep(connectries * 2);
}
#if MYSQL_VERSION_ID >= 32200
mysql_init(&mysqlCons[i].connecbuf);
if (mysql_real_connect(&mysqlCons[i].connecbuf, host, user, passwd,
0, port, 0, 0) != 0) {
#else
if (port) mysql_port = port;
if (mysql_connect(&mysqlCons[i].connecbuf, host, user, passwd) != 0) {
#endif
/* Success */
connect = mysqlCons + i;
set_con(connect, host, 0, user, passwd, port);
break;
}
}
if (connect == 0)
mysqlServerConfl (&mysqlCons[i].connecbuf);
return connect;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Connect
* Implements the mysqlconnect command:
* usage: mysqlconnect ?server-host?
*
* Results:
* handle - a character string of newly open handle
* TCL_OK - connect successful
* TCL_ERROR - connect not successful - error message returned
*/
int
Mysqltcl_Connect (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand = -1;
int port = 0;
int i;
char buf[MYSQL_BUFF_SIZE];
MysqlCon *connect ;
CONST84 char *host = 0, *user = 0, *passwd = 0;
/* Pro-forma check (should never fail). */
if (mysqlPrologue (interp, argc, argv, 1, NULL, "?hostname?") < 0)
return TCL_ERROR;
#ifdef HANDLE_DEBUG
dump_handles(stdout);
#endif /* HANDLE_DEBUG */
/* Find an unused upper level handle */
for (i = 0; i < MYSQL_HANDLES; i++) {
if (mysqlHandle[i].connection == 0) {
hand = i;
break;
}
}
if (hand == -1)
return mysqlPrimConfl ("no mySQL handles available");
if (argc >= 2 && *argv[1]) {
host = argv[1];
}
if (argc >= 3 && *argv[2]) {
user = argv[2];
}
if (argc >= 4 && *argv[3]) {
passwd = argv[3];
}
/* Setting the mysql port number. This used to be stored in a global in
* libmysqlclient.c. We should make it easier to pass the port as a
* parameter now that mysql_real_connect() takes one (it used to be a global)
* but the interface to this proc is already too ugly, so we use the env
*/
{
char *pp = getenv("MYSQL_TCP_PORT");
if (pp)
sscanf(pp, "%u", &port);
}
/* Look for a connection to the same host. Don't bother with the db.
this means that, at "use" time, we may have to allocate another
connection in case of conflict */
connect = 0;
for (i = 0; i < MYSQL_CONS; i++) {
if (mysqlCons[i].use_count > 0 && !nllstrcmp(host, mysqlCons[i].host)) {
connect = mysqlCons + i;
break;
}
}
if (connect == 0) {
/* No connection to this host currently exists. Try to create one */
connect = tryallocconnect(host, user, passwd, port);
if (connect == 0) {
mysqlHandle[hand].connection = 0;
return TCL_ERROR;
}
}
use_con(mysqlHandle + hand, connect);
/* Construct handle and return. */
sprintf(buf, "%s%d", MysqlHandlePrefix, hand);
Tcl_SetResult(interp, buf, TCL_VOLATILE);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Use
* Implements the mysqluse command:
* usage: mysqluse handle dbname
*
* results:
* Sets current database to dbname.
*/
int
Mysqltcl_Use (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand;
int i;
MysqlCon *olcon;
MysqlCon *newcon;
CONST84 char *host;
CONST84 char *database;
if ((hand = mysqlPrologue(interp, argc, argv, 3, get_handle_conn,
"handle dbname")) < 0)
return TCL_ERROR;
database = argv[2];
if (database[0] == 0)
return mysqlPrimConfl ("null database") ;
if (strlen(database) >= MYSQL_NAME_LEN)
return mysqlPrimConfl ("database name too long") ;
olcon = mysqlHandle[hand].connection;
host = olcon->host;
/* If old connection is not shared, just use it */
if (olcon->use_count == 1) {
newcon = olcon;
goto gotnewcon;
}
/* Look for an entry with the right host+db, or no db */
for (i = 0; i < MYSQL_CONS; i++) {
newcon = mysqlCons + i;
if (newcon->use_count > 0 && !nllstrcmp(newcon->host, host) &&
(newcon->database == 0 || !nllstrcmp(newcon->database, database)))
goto gotnewcon;
}
/* Try to allocate a new connection */
if ((newcon = tryallocconnect(olcon->host, olcon->user,
olcon->passwd, olcon->port)) == 0) {
return TCL_ERROR;
}
gotnewcon:
if (nllstrcmp(newcon->database, database)) {
if (mysql_select_db(&newcon->connecbuf, database) < 0) {
if (try_recon(newcon) < 0 ||
mysql_select_db(&newcon->connecbuf, database) < 0)
return mysqlServerConfl (&newcon->connecbuf) ;
}
set_con(newcon, 0, database, 0, 0, 0);
}
/* Hold new con. Order of the following is important: might be the
* same connection, must hold it before release lest it be closed */
use_con(mysqlHandle + hand, newcon);
/* Release old connection */
release_con(olcon);
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2
{
const char *mysqlcharset;
/* Note: we dont use mysql_character_set_name for now because it returns
stuff like 'latin1' which we'd need to convert to tcl's iso8859-1.
It would be relatively easy to build a conversion table, when I'll be
less lazy */
#if 0 && MYSQL_VERSION_ID >= 32321
mysqlcharset = mysql_character_set_name(&newcon->connecbuf);
#else
mysqlcharset = "iso8859-1";
#endif
newcon->tclenc = Tcl_GetEncoding(interp, mysqlcharset);
if (newcon->tclenc == 0) {
Tcl_AppendResult(interp, "TCL cannot use db charset ", mysqlcharset, 0);
return TCL_ERROR;
}
}
#endif /* TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2 */
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Sel
* Implements the mysqlsel command:
* usage: mysqlsel handle sel-query
*
* results:
*
* SIDE EFFECT: Flushes any pending result, even in case of conflict.
* Stores new results.
*/
int Mysqltcl_Sel (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand;
Tcl_DString ds;
CONST84 char *stmt;
if ((hand = mysqlPrologue(interp, argc, argv, 3, get_handle_db,
"handle sel-query")) < 0)
return TCL_ERROR;
/* Flush any previous result. */
if (mysqlHandle[hand].result != NULL) {
mysql_free_result (mysqlHandle[hand].result) ;
mysqlHandle[hand].result = NULL ;
}
stmt = argv[2];
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2
Tcl_DStringInit(&ds);
Tcl_UtfToExternalDString(mysqlHandle[hand].connection->tclenc,
stmt, strlen(stmt), &ds);
stmt = Tcl_DStringValue(&ds);
#endif /* TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2 */
#ifdef HANDLE_DEBUG
freeZ(mysqlHandle[hand].stmt);
mysqlHandle[hand].stmt = strdup(stmt);
#endif /* HANDLE_DEBUG */
if (mysql_query (MYSQLCON(hand), stmt) < 0) {
if (try_recon(mysqlHandle[hand].connection) < 0 ||
mysql_query (MYSQLCON(hand), stmt) < 0)
return mysqlServerConfl (MYSQLCON(hand)) ;
}
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2
Tcl_DStringFree(&ds);
#endif /* TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2 */
if ((mysqlHandle[hand].result = mysql_store_result(MYSQLCON(hand)))== NULL) {
Tcl_SetResult(interp, "-1", TCL_STATIC);
} else {
char buf[30];
mysqlHandle[hand].res_count = mysql_num_rows (mysqlHandle[hand].result) ;
mysqlHandle[hand].col_count = mysql_num_fields (mysqlHandle[hand].result) ;
(void)sprintf (buf, "%d", mysqlHandle[hand].res_count) ;
Tcl_SetResult(interp, buf, TCL_STATIC);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Exec
* Implements the mysqlexec command:
* usage: mysqlexec handle sql-statement
*
* Results:
*
* SIDE EFFECT: Flushes any pending result, even in case of conflict.
*/
int
Mysqltcl_Exec (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand;
int num_rows;
const char *stmt;
Tcl_DString ds;
if ((hand = mysqlPrologue(interp, argc, argv, 3, get_handle_db,
"handle sql-statement")) < 0)
return TCL_ERROR;
/* Flush any previous result. */
if (mysqlHandle[hand].result != NULL) {
mysql_free_result (mysqlHandle[hand].result) ;
mysqlHandle[hand].result = NULL ;
}
stmt = argv[2];
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2
Tcl_DStringInit(&ds);
Tcl_UtfToExternalDString(mysqlHandle[hand].connection->tclenc,
stmt, strlen(stmt), &ds);
stmt = Tcl_DStringValue(&ds);
#endif /* TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2 */
#ifdef HANDLE_DEBUG
freeZ(mysqlHandle[hand].stmt);
mysqlHandle[hand].stmt = strdup(stmt);
#endif /* HANDLE_DEBUG */
if (mysql_query(MYSQLCON(hand), stmt) < 0) {
if (try_recon(mysqlHandle[hand].connection) < 0 ||
mysql_query(MYSQLCON(hand), stmt) < 0)
return mysqlServerConfl(MYSQLCON(hand));
}
#if TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2
Tcl_DStringFree(&ds);
#endif /* TCL_MAJOR_VERSION >= 8 && TCL_MINOR_VERSION >= 2 */
num_rows = mysql_affected_rows(MYSQLCON(hand));
{
char buf[30];
(void)sprintf (buf, "%d", num_rows) ;
Tcl_SetResult(interp, buf, TCL_STATIC);
}
if (mysql_insert_id(MYSQLCON(hand)) != 0)
mysqlHandle[hand].last_insert_id = mysql_insert_id(MYSQLCON(hand));
return TCL_OK ;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Next
* Implements the mysqlnext command:
* usage: mysqlnext handle
*
* results:
* next row from pending results as tcl list, or null list.
*/
int
Mysqltcl_Next (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand;
int idx ;
MYSQL_ROW row ;
char* val ;
if ((hand = mysqlPrologue(interp, argc, argv, 2, get_handle_res,
"handle")) < 0)
return TCL_ERROR;
if (mysqlHandle[hand].res_count == 0)
return TCL_OK ;
else if ((row = mysql_fetch_row (mysqlHandle[hand].result)) == NULL) {
mysqlHandle[hand].res_count = 0 ;
return mysqlPrimConfl ("result counter out of sync") ;
} else
mysqlHandle[hand].res_count-- ;
for (idx = 0 ; idx < mysqlHandle[hand].col_count ; idx++) {
if ((val = *row++) == NULL)
val = MysqlNullvalue ;
Tcl_AppendElement (interp, val) ;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Seek
* Implements the mysqlseek command:
* usage: mysqlseek handle rownumber
*
* results:
* number of remaining rows
*/
int
Mysqltcl_Seek (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand;
int res;
int row;
int total;
if ((hand = mysqlPrologue(interp, argc, argv, 3, get_handle_res,
" handle row-index")) < 0)
return TCL_ERROR;
if ((res = Tcl_GetInt (interp, argv[2], &row)) != TCL_OK)
return res;
total = mysql_num_rows (mysqlHandle[hand].result);
if (total + row < 0) {
mysql_data_seek (mysqlHandle[hand].result, 0);
mysqlHandle[hand].res_count = total;
} else if (row < 0) {
mysql_data_seek (mysqlHandle[hand].result, total + row);
mysqlHandle[hand].res_count = -row;
} else if (row >= total) {
mysql_data_seek (mysqlHandle[hand].result, row);
mysqlHandle[hand].res_count = 0;
} else {
mysql_data_seek (mysqlHandle[hand].result, row);
mysqlHandle[hand].res_count = total - row;
}
{
char buf[30];
(void)sprintf (buf, "%d", mysqlHandle[hand].res_count) ;
Tcl_SetResult(interp, buf, TCL_STATIC);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Map
* Implements the mysqlmap command:
* usage: mysqlmap handle binding-list script
*
* Results:
* SIDE EFFECT: For each row the column values are bound to the variables
* in the binding list and the script is evaluated.
* The variables are created in the current context.
* NOTE: mysqlmap works very much like a 'foreach' construct.
* The 'continue' and 'break' commands may be used with their usual effect.
*/
int
Mysqltcl_Map (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int code;
int count;
int hand;
int idx;
int listArgc ;
CONST84 char** listArgv ;
MYSQL_ROW row ;
char* val ;
if ((hand = mysqlPrologue(interp, argc, argv, 4, get_handle_res,
"handle binding-list script")) < 0)
return TCL_ERROR;
if (Tcl_SplitList (interp, argv[2], &listArgc, &listArgv) != TCL_OK)
return TCL_ERROR ;
if (listArgc > mysqlHandle[hand].col_count) {
ckfree ((char*)listArgv) ;
return mysqlPrimConfl ("too many variables in binding list") ;
} else
count = (listArgc < mysqlHandle[hand].col_count) ?
listArgc : mysqlHandle[hand].col_count;
while (mysqlHandle[hand].res_count > 0) {
/* Get next row, decrement row counter. */
if ((row = mysql_fetch_row (mysqlHandle[hand].result)) == NULL) {
mysqlHandle[hand].res_count = 0 ;
ckfree ((char*)listArgv) ;
return mysqlPrimConfl ("result counter out of sync") ;
} else
mysqlHandle[hand].res_count-- ;
/* Bind variables to column values. */
for (idx = 0; idx < count; idx++) {
if (listArgv[idx][0] != '-') {
if ((val = *row++) == NULL)
val = MysqlNullvalue ;
if (Tcl_SetVar (interp, listArgv[idx], val, TCL_LEAVE_ERR_MSG)
== NULL) {
ckfree ((char*)listArgv) ;
return TCL_ERROR ;
}
} else
row++ ;
}
/* Evaluate the script. */
if ((code = Tcl_Eval (interp, argv[3])) != TCL_OK) {
switch (code) {
case TCL_CONTINUE:
continue ;
break ;
case TCL_BREAK:
ckfree ((char*)listArgv) ;
return TCL_OK ;
break ;
default:
ckfree ((char*)listArgv) ;
return code ;
}
}
}
ckfree ((char*)listArgv) ;
return TCL_OK ;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Info
* Implements the mysqlinfo command:
* usage: mysqlinfo handle option
*
*/
int
Mysqltcl_Info (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
char buf[MYSQL_BUFF_SIZE];
int count ;
int hand ;
int idx ;
MYSQL_RES* list ;
MYSQL_ROW row ;
char* val ;
/* We can't fully check the handle at this stage. */
if ((hand = mysqlPrologue(interp, argc, argv, 3, get_handle_plain,
"handle option")) < 0)
return TCL_ERROR;
for (idx = 0;
idx <= MYSQL_INF_OPT_MAX && strcmp (argv[2], MysqlDbOpt[idx]) != 0;
idx++);
/* First check the handle. Checking depends on the option. */
switch (idx) {
case MYSQL_INFNAME_OPT:
case MYSQL_INFTABLES_OPT:
hand = get_handle_db (argv[1]) ;
break ;
case MYSQL_INFNAMEQ_OPT:
if ((hand = get_handle_conn (argv[1])) >= 0) {
if (mysqlHandle[hand].connection->database == 0)
return TCL_OK ; /* Return empty string if no current db. */
}
break ;
case MYSQL_INFHOST_OPT:
case MYSQL_INFLIST_OPT:
hand = get_handle_conn (argv[1]) ;
break ;
case MYSQL_INFHOSTQ_OPT:
if (MYSQLCON(hand) == NULL)
return TCL_OK ; /* Return empty string if not connected. */
break ;
default: /* unknown option */
sprintf (buf, "'%s' unknown option", argv[2]);
return mysqlPrimConfl (buf) ;
}
if (hand < 0)
return TCL_ERROR ;
/* Handle OK, return the requested info. */
switch (idx) {
case MYSQL_INFNAME_OPT:
case MYSQL_INFNAMEQ_OPT:
Tcl_SetResult(interp, mysqlHandle[hand].connection->database, TCL_STATIC);
break ;
case MYSQL_INFTABLES_OPT:
if ((list= mysql_list_tables (MYSQLCON(hand), NULL)) == NULL)
return mysqlPrimConfl("could not access table names; server may have gone away") ;
for (count = mysql_num_rows (list); count > 0; count--) {
val = *(row = mysql_fetch_row (list)) ;
Tcl_AppendElement (interp, (val == NULL)?"":val) ;
}
mysql_free_result (list) ;
break ;
case MYSQL_INFHOST_OPT:
case MYSQL_INFHOSTQ_OPT:
Tcl_SetResult(interp, mysqlHandle[hand].connection->host, TCL_STATIC) ;
break ;
case MYSQL_INFLIST_OPT:
if ((list = mysql_list_dbs (MYSQLCON(hand), NULL)) == NULL)
return mysqlPrimConfl ("could not access database names; server may have gone away") ;
for (count = mysql_num_rows (list); count > 0; count--) {
val = *(row = mysql_fetch_row (list)) ;
Tcl_AppendElement (interp, (val == NULL)?"":val) ;
}
mysql_free_result (list) ;
break ;
default: /* should never happen */
return mysqlPrimConfl ("weirdness in Mysqltcl_Info") ;
}
return TCL_OK ;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Result
* Implements the mysqlresult command:
* usage: mysqlresult handle option
*
*/
int
Mysqltcl_Result (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
char buf[MYSQL_BUFF_SIZE];
int hand ;
int idx ;
/* We can't fully check the handle at this stage. */
if ((hand = mysqlPrologue(interp, argc, argv, 3, get_handle_plain,
" handle option")) < 0)
return TCL_ERROR;
for (idx = 0;
idx <= MYSQL_RES_OPT_MAX && strcmp (argv[2], MysqlResultOpt[idx]) != 0;
idx++) ;
/* First check the handle. Checking depends on the option. */
switch (idx) {
case MYSQL_RESROWS_OPT:
case MYSQL_RESCOLS_OPT:
case MYSQL_RESCUR_OPT:
hand = get_handle_res (argv[1]) ;
break ;
case MYSQL_RESROWSQ_OPT:
case MYSQL_RESCOLSQ_OPT:
case MYSQL_RESCURQ_OPT:
if ((hand = get_handle_db (argv[1])) >= 0) {
if (mysqlHandle[hand].result == NULL)
return TCL_OK ; /* Return empty string if no pending result. */
}
break ;
default: /* unknown option */
sprintf (buf, "'%s' unknown option", argv[2]);
return mysqlPrimConfl (buf) ;
}
if (hand < 0)
return TCL_ERROR ;
/* Handle OK; return requested info. */
switch (idx) {
case MYSQL_RESROWS_OPT:
case MYSQL_RESROWSQ_OPT:
{
char buf[30];
sprintf(buf, "%d", mysqlHandle[hand].res_count);
Tcl_SetResult(interp, buf, TCL_STATIC);
}
break ;
case MYSQL_RESCOLS_OPT:
case MYSQL_RESCOLSQ_OPT:
{
char buf[30];
sprintf (buf, "%d", mysqlHandle[hand].col_count) ;
Tcl_SetResult(interp, buf, TCL_STATIC);
}
break ;
case MYSQL_RESCUR_OPT:
case MYSQL_RESCURQ_OPT:
{
char buf[30];
sprintf (buf, "%d",
(int)(mysql_num_rows (mysqlHandle[hand].result)
- mysqlHandle[hand].res_count)) ;
Tcl_SetResult(interp, buf, TCL_STATIC);
}
default: /* unknown option: not possible, already checked, but anyway... */
sprintf (buf, "'%s' unknown option", argv[2]);
return mysqlPrimConfl (buf) ;
}
return TCL_OK ;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Col
* Implements the mysqlcol command:
* usage: mysqlcol handle table-name option ?option ...?
* mysqlcol handle -current option ?option ...?
* '-current' can only be used if there is a pending result.
*
* results:
* List of lists containing column attributes.
* If a single attribute is requested the result is a simple list.
*
* SIDE EFFECT: '-current' disturbs the field position of the result.
*/
int Mysqltcl_Col (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
char buf[MYSQL_BUFF_SIZE];
int coln ;
int conflict ;
int current_db ;
int hand;
int idx ;
int listArgc ;
CONST84 char** listArgv ;
MYSQL_FIELD* fld ;
MYSQL_RES* result ;
char* sep ;
int simple ;
/* This check is enough only without '-current'. */
if ((hand = mysqlPrologue(interp, argc, argv, 4, get_handle_db,
"handle table-name option ?option ...?")) < 0)
return TCL_ERROR;
/* Fetch column info.
* Two ways: explicit database and table names, or current.
*/
current_db = strcmp (argv[2], "-current") == 0 ;
if (current_db) {
if ((hand = get_handle_res (argv[1])) < 0)
return TCL_ERROR ;
else
result = mysqlHandle[hand].result ;
} else {
if ((result = mysql_list_fields (MYSQLCON(hand),
argv[2], NULL)) == NULL) {
sprintf (buf, "no column info for table '%s'; %s", argv[2],
"server may have gone away") ;
return mysqlPrimConfl (buf) ;
}
}
/* Must examine the first specifier at this point. */
if (Tcl_SplitList (interp, argv[3], &listArgc, &listArgv) != TCL_OK)
return TCL_ERROR ;
conflict = 0 ;
simple = (argc == 4) && (listArgc == 1) ;
if (simple) {
mysql_field_seek (result, 0) ;
while ((fld = mysql_fetch_field (result)) != NULL)
if (mysqlColinfo (interp, fld, argv[3])) {
conflict = 1 ;
break ;
}
} else if (listArgc > 1) {
mysql_field_seek (result, 0) ;
for (sep = "{"; (fld = mysql_fetch_field (result)) != NULL; sep = " {") {
Tcl_AppendResult (interp, sep, (char*)NULL) ;
for (coln = 0; coln < listArgc; coln++)
if (mysqlColinfo (interp, fld, listArgv[coln])) {
conflict = 1 ;
break ;
}
if (conflict)
break ;
Tcl_AppendResult (interp, "}", (char*)NULL) ;
}
ckfree ((char*)listArgv) ;
} else {
ckfree ((char*)listArgv) ; /* listArgc == 1, no splitting */
for (idx = 3, sep = "{"; idx < argc; idx++, sep = " {") {
Tcl_AppendResult (interp, sep, (char*)NULL) ;
mysql_field_seek (result, 0) ;
while ((fld = mysql_fetch_field (result)) != NULL)
if (mysqlColinfo (interp, fld, argv[idx])) {
conflict = 1 ;
break ;
}
if (conflict)
break ;
Tcl_AppendResult (interp, "}", (char*)NULL) ;
}
}
if (!current_db)
mysql_free_result (result) ;
return (conflict)?TCL_ERROR:TCL_OK ;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_State
* Implements the mysqlstate command:
* usage: mysqlstate ?-numeric? handle
*
*/
int
Mysqltcl_State (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hi;
CONST84 char* hp ;
int numeric ;
const char* res ;
if (mysqlPrologue(interp, argc, argv, 2, NULL, "?-numeric? handle") < 0)
return TCL_ERROR;
if ((numeric = (strcmp (argv[1], "-numeric") == 0)) && argc < 3)
return mysqlPrimConfl ("handle required") ;
hp = (numeric)?argv[2]:argv[1] ;
if ((hi=get_handle_plain(hp)) < 0)
res = (numeric)?"0":"NOT_A_HANDLE" ;
else if (mysqlHandle[hi].connection == 0)
res = (numeric)?"1":"UNCONNECTED" ;
else if (mysqlHandle[hi].connection->database == 0)
res = (numeric)?"2":"CONNECTED" ;
else if (mysqlHandle[hi].result == NULL)
res = (numeric)?"3":"IN_USE" ;
else
res = (numeric)?"4":"RESULT_PENDING" ;
Tcl_SetResult(interp, (char *)res, TCL_STATIC) ;
return TCL_OK ;
}
/*
* Mysqltcl_InsertId
* usage: mysqlinsertid handle
* Returns the auto increment id of the last INSERT statement
*/
int
Mysqltcl_InsertId (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand;
if ((hand = mysqlPrologue(interp, argc, argv, 2, get_handle_conn,
"handle")) < 0)
return TCL_ERROR;
{char buf[30];
(void)sprintf (buf, "%d", mysqlHandle[hand].last_insert_id);
Tcl_SetResult(interp, buf, TCL_STATIC);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Mysqltcl_Close --
* Implements the mysqlclose command:
* usage: mysqlclose ?handle?
*
* results:
* null string
*/
int
Mysqltcl_Close (ClientData clientData,
Tcl_Interp *interp,
int argc,
CONST84 char **argv)
{
int hand;
/* If handle omitted, close all connections. */
if (argc == 1) {
Mysqltcl_Kill ((ClientData)NULL) ;
return TCL_OK ;
}
if ((hand = mysqlPrologue(interp, argc, argv, 2, get_handle_conn,
"handle")) < 0)
return TCL_ERROR;
if (mysqlHandle[hand].result != NULL)
mysql_free_result (mysqlHandle[hand].result) ;
#ifdef HANDLE_DEBUG
freeZ(mysqlHandle[hand].stmt);
#endif
release_con(mysqlHandle[hand].connection);
memset(&mysqlHandle[hand], 0, sizeof(struct MysqlTclHandle));
return TCL_OK;
}