OpenFPGA/libs/EXTERNAL/tcl8.6.12/pkgs/tdbcmysql1.1.3/generic/tdbcmysql.c

3796 lines
111 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* tdbcmysql.c --
*
* Bridge between TDBC (Tcl DataBase Connectivity) and MYSQL.
*
* Copyright (c) 2008, 2009 by Kevin B. Kenny.
*
* Please refer to the file, 'license.terms' for the conditions on
* redistribution of this file and for a DISCLAIMER OF ALL WARRANTIES.
*
* $Id: $
*
*-----------------------------------------------------------------------------
*/
#ifdef _MSC_VER
# define _CRT_SECURE_NO_WARNINGS
#endif
#include <tcl.h>
#include <tclOO.h>
#include <tdbc.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_STDINT_H
# include <stdint.h>
#endif
#include "int2ptr_ptr2int.h"
#include "fakemysql.h"
/* Static data contained in this file */
TCL_DECLARE_MUTEX(mysqlMutex); /* Mutex protecting the global environment
* and its reference count */
static int mysqlRefCount = 0; /* Reference count on the global environment */
Tcl_LoadHandle mysqlLoadHandle = NULL;
/* Handle to the MySQL library */
unsigned long mysqlClientVersion;
/* Version number of MySQL */
/*
* Objects to create within the literal pool
*/
const char* LiteralValues[] = {
"",
"0",
"1",
"direction",
"in",
"inout",
"name",
"nullable",
"out",
"precision",
"scale",
"type",
NULL
};
enum LiteralIndex {
LIT_EMPTY,
LIT_0,
LIT_1,
LIT_DIRECTION,
LIT_IN,
LIT_INOUT,
LIT_NAME,
LIT_NULLABLE,
LIT_OUT,
LIT_PRECISION,
LIT_SCALE,
LIT_TYPE,
LIT__END
};
/*
* Structure that holds per-interpreter data for the MYSQL package.
*/
typedef struct PerInterpData {
size_t refCount; /* Reference count */
Tcl_Obj* literals[LIT__END];
/* Literal pool */
Tcl_HashTable typeNumHash; /* Lookup table for type numbers */
} PerInterpData;
#define IncrPerInterpRefCount(x) \
do { \
++((x)->refCount); \
} while(0)
#define DecrPerInterpRefCount(x) \
do { \
PerInterpData* _pidata = x; \
if (((_pidata->refCount))-- <= 1) { \
DeletePerInterpData(_pidata); \
} \
} while(0)
/*
* Structure that carries the data for an MYSQL connection
*
* The ConnectionData structure is refcounted to simplify the
* destruction of statements associated with a connection.
* When a connection is destroyed, the subordinate namespace that
* contains its statements is taken down, destroying them. It's
* not safe to take down the ConnectionData until nothing is
* referring to it, which avoids taking down the hDBC until the
* other objects that refer to it vanish.
*/
typedef struct ConnectionData {
size_t refCount; /* Reference count. */
PerInterpData* pidata; /* Per-interpreter data */
MYSQL* mysqlPtr; /* MySql connection handle */
unsigned int nCollations; /* Number of collations defined */
int* collationSizes; /* Character lengths indexed by collation ID */
int flags;
} ConnectionData;
/*
* Flags for the state of an MYSQL connection
*/
#define CONN_FLAG_AUTOCOMMIT 0x1 /* Autocommit is set */
#define CONN_FLAG_IN_XCN 0x2 /* Transaction is in progress */
#define CONN_FLAG_INTERACTIVE 0x4 /* -interactive requested at connect */
#define IncrConnectionRefCount(x) \
do { \
++((x)->refCount); \
} while(0)
#define DecrConnectionRefCount(x) \
do { \
ConnectionData* conn = x; \
if (((conn->refCount)--) <= 01) { \
DeleteConnection(conn); \
} \
} while(0)
/*
* Structure that carries the data for a MySQL prepared statement.
*
* Just as with connections, statements need to defer taking down
* their client data until other objects (i.e., result sets) that
* refer to them have had a chance to clean up. Hence, this
* structure is reference counted as well.
*/
typedef struct StatementData {
size_t refCount; /* Reference count */
ConnectionData* cdata; /* Data for the connection to which this
* statement pertains. */
Tcl_Obj* subVars; /* List of variables to be substituted, in the
* order in which they appear in the
* statement */
struct ParamData *params; /* Data types and attributes of parameters */
Tcl_Obj* nativeSql; /* Native SQL statement to pass into
* MySQL */
MYSQL_STMT* stmtPtr; /* MySQL statement handle */
MYSQL_RES* metadataPtr; /* MySQL result set metadata */
Tcl_Obj* columnNames; /* Column names in the result set */
int flags;
} StatementData;
#define IncrStatementRefCount(x) \
do { \
++((x)->refCount); \
} while (0)
#define DecrStatementRefCount(x) \
do { \
StatementData* stmt = (x); \
if ((stmt->refCount--) <= 1) { \
DeleteStatement(stmt); \
} \
} while(0)
/* Flags in the 'StatementData->flags' word */
#define STMT_FLAG_BUSY 0x1 /* Statement handle is in use */
/*
* Structure describing the data types of substituted parameters in
* a SQL statement.
*/
typedef struct ParamData {
int flags; /* Flags regarding the parameters - see below */
int dataType; /* Data type */
int precision; /* Size of the expected data */
int scale; /* Digits after decimal point of the
* expected data */
} ParamData;
#define PARAM_KNOWN 1<<0 /* Something is known about the parameter */
#define PARAM_IN 1<<1 /* Parameter is an input parameter */
#define PARAM_OUT 1<<2 /* Parameter is an output parameter */
/* (Both bits are set if parameter is
* an INOUT parameter) */
#define PARAM_BINARY 1<<3 /* Parameter is binary */
/*
* Structure describing a MySQL result set. The object that the Tcl
* API terms a "result set" actually has to be represented by a MySQL
* "statement", since a MySQL statement can have only one set of results
* at any given time.
*/
typedef struct ResultSetData {
size_t refCount; /* Reference count */
StatementData* sdata; /* Statement that generated this result set */
MYSQL_STMT* stmtPtr; /* Handle to the MySQL statement object */
Tcl_Obj* paramValues; /* List of parameter values */
MYSQL_BIND* paramBindings; /* Parameter bindings */
unsigned long* paramLengths;/* Parameter lengths */
my_ulonglong rowCount; /* Number of affected rows */
my_bool* resultErrors; /* Failure indicators for retrieving columns */
my_bool* resultNulls; /* NULL indicators for retrieving columns */
unsigned long* resultLengths;
/* Byte lengths of retrieved columns */
MYSQL_BIND* resultBindings; /* Bindings controlling column retrieval */
} ResultSetData;
#define IncrResultSetRefCount(x) \
do { \
++((x)->refCount); \
} while (0)
#define DecrResultSetRefCount(x) \
do { \
ResultSetData* rs = (x); \
if (rs->refCount-- <= 1) { \
DeleteResultSet(rs); \
} \
} while(0)
/* Table of MySQL type names */
#define IS_BINARY (1<<16) /* Flag to OR in if a param is binary */
typedef struct MysqlDataType {
const char* name; /* Type name */
int num; /* Type number */
} MysqlDataType;
static const MysqlDataType dataTypes[] = {
{ "tinyint", MYSQL_TYPE_TINY },
{ "smallint", MYSQL_TYPE_SHORT },
{ "integer", MYSQL_TYPE_LONG },
{ "float", MYSQL_TYPE_FLOAT },
{ "real", MYSQL_TYPE_FLOAT },
{ "double", MYSQL_TYPE_DOUBLE },
{ "NULL", MYSQL_TYPE_NULL },
{ "timestamp", MYSQL_TYPE_TIMESTAMP },
{ "bigint", MYSQL_TYPE_LONGLONG },
{ "mediumint", MYSQL_TYPE_INT24 },
{ "date", MYSQL_TYPE_NEWDATE },
{ "date", MYSQL_TYPE_DATE },
{ "time", MYSQL_TYPE_TIME },
{ "datetime", MYSQL_TYPE_DATETIME },
{ "year", MYSQL_TYPE_YEAR },
{ "bit", MYSQL_TYPE_BIT | IS_BINARY },
{ "numeric", MYSQL_TYPE_NEWDECIMAL },
{ "decimal", MYSQL_TYPE_NEWDECIMAL },
{ "numeric", MYSQL_TYPE_DECIMAL },
{ "decimal", MYSQL_TYPE_DECIMAL },
{ "enum", MYSQL_TYPE_ENUM },
{ "set", MYSQL_TYPE_SET },
{ "tinytext", MYSQL_TYPE_TINY_BLOB },
{ "tinyblob", MYSQL_TYPE_TINY_BLOB | IS_BINARY },
{ "mediumtext", MYSQL_TYPE_MEDIUM_BLOB },
{ "mediumblob", MYSQL_TYPE_MEDIUM_BLOB | IS_BINARY },
{ "longtext", MYSQL_TYPE_LONG_BLOB },
{ "longblob", MYSQL_TYPE_LONG_BLOB | IS_BINARY },
{ "text", MYSQL_TYPE_BLOB },
{ "blob", MYSQL_TYPE_BLOB | IS_BINARY },
{ "varbinary", MYSQL_TYPE_VAR_STRING | IS_BINARY },
{ "varchar", MYSQL_TYPE_VAR_STRING },
{ "varbinary", MYSQL_TYPE_VARCHAR | IS_BINARY },
{ "varchar", MYSQL_TYPE_VARCHAR },
{ "binary", MYSQL_TYPE_STRING | IS_BINARY },
{ "char", MYSQL_TYPE_STRING },
{ "geometry", MYSQL_TYPE_GEOMETRY },
{ NULL, 0 }
};
/* Configuration options for MySQL connections */
/* Data types of configuration options */
enum OptType {
TYPE_STRING, /* Arbitrary character string */
TYPE_FLAG, /* Boolean flag */
TYPE_ENCODING, /* Encoding name */
TYPE_ISOLATION, /* Transaction isolation level */
TYPE_PORT, /* Port number */
TYPE_READONLY, /* Read-only indicator */
TYPE_TIMEOUT /* Timeout value */
};
/* Locations of the string options in the string array */
enum OptStringIndex {
INDX_DB, INDX_HOST, INDX_PASSWD, INDX_SOCKET,
INDX_SSLCA, INDX_SSLCAPATH, INDX_SSLCERT, INDX_SSLCIPHER, INDX_SSLKEY,
INDX_USER,
INDX_MAX
};
/* Flags in the configuration table */
#define CONN_OPT_FLAG_MOD 0x1 /* Configuration value changable at runtime */
#define CONN_OPT_FLAG_SSL 0x2 /* Configuration change requires setting
* SSL options */
#define CONN_OPT_FLAG_ALIAS 0x4 /* Configuration option is an alias */
/* Table of configuration options */
static const struct {
const char * name; /* Option name */
enum OptType type; /* Option data type */
int info; /* Option index or flag value */
int flags; /* Flags - modifiable; SSL related; is an alias */
const char* query; /* How to determine the option value? */
} ConnOptions [] = {
{ "-compress", TYPE_FLAG, CLIENT_COMPRESS, 0,
"SELECT '', @@SLAVE_COMPRESSED_PROTOCOL" },
{ "-database", TYPE_STRING, INDX_DB, CONN_OPT_FLAG_MOD,
"SELECT '', DATABASE();"},
{ "-db", TYPE_STRING, INDX_DB, CONN_OPT_FLAG_MOD
| CONN_OPT_FLAG_ALIAS,
"SELECT '', DATABASE()" },
{ "-encoding", TYPE_ENCODING, 0, 0,
"SELECT '', 'utf-8'" },
{ "-host", TYPE_STRING, INDX_HOST, 0,
"SHOW SESSION VARIABLES WHERE VARIABLE_NAME = 'hostname'" },
{ "-interactive", TYPE_FLAG, CLIENT_INTERACTIVE, 0,
"SELECT '', 0" },
{ "-isolation", TYPE_ISOLATION, 0, CONN_OPT_FLAG_MOD,
"SELECT '', LCASE(REPLACE(@@TX_ISOLATION, '-', ''))" },
{ "-passwd", TYPE_STRING, INDX_PASSWD, CONN_OPT_FLAG_MOD
| CONN_OPT_FLAG_ALIAS,
"SELECT '', ''" },
{ "-password", TYPE_STRING, INDX_PASSWD, CONN_OPT_FLAG_MOD,
"SELECT '', ''" },
{ "-port", TYPE_PORT, 0, 0,
"SHOW SESSION VARIABLES WHERE VARIABLE_NAME = 'port'" },
{ "-readonly", TYPE_READONLY, 0, 0,
"SELECT '', 0" },
{ "-socket", TYPE_STRING, INDX_SOCKET, 0,
"SHOW SESSION VARIABLES WHERE VARIABLE_NAME = 'socket'" },
{ "-ssl_ca", TYPE_STRING, INDX_SSLCA, CONN_OPT_FLAG_SSL,
"SELECT '', @@SSL_CA"},
{ "-ssl_capath", TYPE_STRING, INDX_SSLCAPATH, CONN_OPT_FLAG_SSL,
"SELECT '', @@SSL_CAPATH" },
{ "-ssl_cert", TYPE_STRING, INDX_SSLCERT, CONN_OPT_FLAG_SSL,
"SELECT '', @@SSL_CERT" },
{ "-ssl_cipher", TYPE_STRING, INDX_SSLCIPHER, CONN_OPT_FLAG_SSL,
"SELECT '', @@SSL_CIPHER" },
{ "-ssl_cypher", TYPE_STRING, INDX_SSLCIPHER, CONN_OPT_FLAG_SSL
| CONN_OPT_FLAG_ALIAS,
"SELECT '', @@SSL_CIPHER" },
{ "-ssl_key", TYPE_STRING, INDX_SSLKEY, CONN_OPT_FLAG_SSL,
"SELECT '', @@SSL_KEY" },
{ "-timeout", TYPE_TIMEOUT, 0, CONN_OPT_FLAG_MOD,
"SELECT '', @@WAIT_TIMEOUT" },
{ "-user", TYPE_STRING, INDX_USER, CONN_OPT_FLAG_MOD,
"SELECT '', USER()" },
{ NULL, TYPE_STRING, 0, 0, NULL }
};
/* Tables of isolation levels: Tcl, SQL, and MySQL 'tx_isolation' */
static const char *const TclIsolationLevels[] = {
"readuncommitted",
"readcommitted",
"repeatableread",
"serializable",
NULL
};
static const char *const SqlIsolationLevels[] = {
"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED",
"SET TRANSACTION ISOLATION LEVEL READ COMMITTED",
"SET TRANSACTION ISOLATION LEVEL REPEATABLE READ",
"SET TRANSACTION ISOLATION LEVEL SERIALIZABLE",
NULL
};
enum IsolationLevel {
ISOL_READ_UNCOMMITTED,
ISOL_READ_COMMITTED,
ISOL_REPEATABLE_READ,
ISOL_SERIALIZABLE,
ISOL_NONE = -1
};
/* Declarations of static functions appearing in this file */
static MYSQL_BIND* MysqlBindAlloc(int nBindings);
static MYSQL_BIND* MysqlBindIndex(MYSQL_BIND* b, int i);
static void* MysqlBindAllocBuffer(MYSQL_BIND* b, int i, unsigned long len);
static void MysqlBindFreeBuffer(MYSQL_BIND* b, int i);
static void MysqlBindSetBufferType(MYSQL_BIND* b, int i,
enum enum_field_types t);
static void* MysqlBindGetBuffer(MYSQL_BIND* b, int i);
static unsigned long MysqlBindGetBufferLength(MYSQL_BIND* b, int i);
static void MysqlBindSetLength(MYSQL_BIND* b, int i, unsigned long* p);
static void MysqlBindSetIsNull(MYSQL_BIND* b, int i, my_bool* p);
static void MysqlBindSetError(MYSQL_BIND* b, int i, my_bool* p);
static MYSQL_FIELD* MysqlFieldIndex(MYSQL_FIELD* fields, int i);
static void TransferMysqlError(Tcl_Interp* interp, MYSQL* mysqlPtr);
static void TransferMysqlStmtError(Tcl_Interp* interp, MYSQL_STMT* mysqlPtr);
static Tcl_Obj* QueryConnectionOption(ConnectionData* cdata, Tcl_Interp* interp,
int optionNum);
static int ConfigureConnection(ConnectionData* cdata, Tcl_Interp* interp,
int objc, Tcl_Obj *const objv[], int skip);
static int ConnectionConstructor(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionBegintransactionMethod(ClientData clientData,
Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionColumnsMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionCommitMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionConfigureMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionEvaldirectMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionNeedCollationInfoMethod(ClientData clientData,
Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionRollbackMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionSetCollationInfoMethod(ClientData clientData,
Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ConnectionTablesMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static void DeleteConnectionMetadata(ClientData clientData);
static void DeleteConnection(ConnectionData* cdata);
static int CloneConnection(Tcl_Interp* interp, ClientData oldClientData,
ClientData* newClientData);
static StatementData* NewStatement(ConnectionData* cdata);
static MYSQL_STMT* AllocAndPrepareStatement(Tcl_Interp* interp,
StatementData* sdata);
static Tcl_Obj* ResultDescToTcl(MYSQL_RES* resultDesc, int flags);
static int StatementConstructor(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int StatementParamtypeMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int StatementParamsMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static void DeleteStatementMetadata(ClientData clientData);
static void DeleteStatement(StatementData* sdata);
static int CloneStatement(Tcl_Interp* interp, ClientData oldClientData,
ClientData* newClientData);
static int ResultSetConstructor(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ResultSetColumnsMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ResultSetNextrowMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static int ResultSetRowcountMethod(ClientData clientData, Tcl_Interp* interp,
Tcl_ObjectContext context,
int objc, Tcl_Obj *const objv[]);
static void DeleteResultSetMetadata(ClientData clientData);
static void DeleteResultSet(ResultSetData* rdata);
static int CloneResultSet(Tcl_Interp* interp, ClientData oldClientData,
ClientData* newClientData);
static void DeleteCmd(ClientData clientData);
static int CloneCmd(Tcl_Interp* interp,
ClientData oldMetadata, ClientData* newMetadata);
static void DeletePerInterpData(PerInterpData* pidata);
/* Metadata type that holds connection data */
const static Tcl_ObjectMetadataType connectionDataType = {
TCL_OO_METADATA_VERSION_CURRENT,
/* version */
"ConnectionData", /* name */
DeleteConnectionMetadata, /* deleteProc */
CloneConnection /* cloneProc - should cause an error
* 'cuz connections aren't clonable */
};
/* Metadata type that holds statement data */
const static Tcl_ObjectMetadataType statementDataType = {
TCL_OO_METADATA_VERSION_CURRENT,
/* version */
"StatementData", /* name */
DeleteStatementMetadata, /* deleteProc */
CloneStatement /* cloneProc - should cause an error
* 'cuz statements aren't clonable */
};
/* Metadata type for result set data */
const static Tcl_ObjectMetadataType resultSetDataType = {
TCL_OO_METADATA_VERSION_CURRENT,
/* version */
"ResultSetData", /* name */
DeleteResultSetMetadata, /* deleteProc */
CloneResultSet /* cloneProc - should cause an error
* 'cuz result sets aren't clonable */
};
/* Method types of the connection methods that are implemented in C */
const static Tcl_MethodType ConnectionConstructorType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"CONSTRUCTOR", /* name */
ConnectionConstructor, /* callProc */
DeleteCmd, /* deleteProc */
CloneCmd /* cloneProc */
};
const static Tcl_MethodType ConnectionBegintransactionMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"begintransaction", /* name */
ConnectionBegintransactionMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionColumnsMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"Columns", /* name */
ConnectionColumnsMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionCommitMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"commit", /* name */
ConnectionCommitMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionConfigureMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"configure", /* name */
ConnectionConfigureMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionEvaldirectMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"evaldirect", /* name */
ConnectionEvaldirectMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionNeedCollationInfoMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"NeedCollationInfo", /* name */
ConnectionNeedCollationInfoMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionRollbackMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"rollback", /* name */
ConnectionRollbackMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionSetCollationInfoMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"SetCollationInfo", /* name */
ConnectionSetCollationInfoMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ConnectionTablesMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"tables", /* name */
ConnectionTablesMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType* ConnectionMethods[] = {
&ConnectionBegintransactionMethodType,
&ConnectionColumnsMethodType,
&ConnectionCommitMethodType,
&ConnectionConfigureMethodType,
&ConnectionEvaldirectMethodType,
&ConnectionNeedCollationInfoMethodType,
&ConnectionRollbackMethodType,
&ConnectionSetCollationInfoMethodType,
&ConnectionTablesMethodType,
NULL
};
/* Method types of the statement methods that are implemented in C */
const static Tcl_MethodType StatementConstructorType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"CONSTRUCTOR", /* name */
StatementConstructor, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType StatementParamsMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"params", /* name */
StatementParamsMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType StatementParamtypeMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"paramtype", /* name */
StatementParamtypeMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
/*
* Methods to create on the statement class.
*/
const static Tcl_MethodType* StatementMethods[] = {
&StatementParamsMethodType,
&StatementParamtypeMethodType,
NULL
};
/* Method types of the result set methods that are implemented in C */
const static Tcl_MethodType ResultSetConstructorType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"CONSTRUCTOR", /* name */
ResultSetConstructor, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ResultSetColumnsMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */ "columns", /* name */
ResultSetColumnsMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ResultSetNextrowMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"nextrow", /* name */
ResultSetNextrowMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
const static Tcl_MethodType ResultSetRowcountMethodType = {
TCL_OO_METHOD_VERSION_CURRENT,
/* version */
"rowcount", /* name */
ResultSetRowcountMethod, /* callProc */
NULL, /* deleteProc */
NULL /* cloneProc */
};
/* Methods to create on the result set class */
const static Tcl_MethodType* ResultSetMethods[] = {
&ResultSetColumnsMethodType,
&ResultSetRowcountMethodType,
NULL
};
/*
*-----------------------------------------------------------------------------
*
* MysqlBindAlloc --
*
* Allocate a number of MYSQL_BIND structures.
*
* Results:
* Returns a pointer to the array of structures, which will be zeroed out.
*
*-----------------------------------------------------------------------------
*/
static MYSQL_BIND*
MysqlBindAlloc(int nBindings)
{
int size;
void* retval = NULL;
if (mysqlClientVersion >= 50100) {
size = sizeof(struct st_mysql_bind_51);
} else {
size = sizeof(struct st_mysql_bind_50);
}
size *= nBindings;
if (size != 0) {
retval = ckalloc(size);
memset(retval, 0, size);
}
return (MYSQL_BIND*) retval;
}
/*
*-----------------------------------------------------------------------------
*
* MysqlBindIndex --
*
* Returns a pointer to one of an array of MYSQL_BIND objects
*
*-----------------------------------------------------------------------------
*/
static MYSQL_BIND*
MysqlBindIndex(
MYSQL_BIND* b, /* Binding array to alter */
int i /* Index in the binding array */
) {
if (mysqlClientVersion >= 50100) {
return (MYSQL_BIND*)(((struct st_mysql_bind_51*) b) + i);
} else {
return (MYSQL_BIND*)(((struct st_mysql_bind_50*) b) + i);
}
}
/*
*-----------------------------------------------------------------------------
*
* MysqlBindAllocBuffer --
*
* Allocates the buffer in a MYSQL_BIND object
*
* Results:
* Returns a pointer to the allocated buffer
*
*-----------------------------------------------------------------------------
*/
static void*
MysqlBindAllocBuffer(
MYSQL_BIND* b, /* Pointer to a binding array */
int i, /* Index into the array */
unsigned long len /* Length of the buffer to allocate or 0 */
) {
void* block = NULL;
if (len != 0) {
block = ckalloc(len);
}
if (mysqlClientVersion >= 50100) {
((struct st_mysql_bind_51*) b)[i].buffer = block;
((struct st_mysql_bind_51*) b)[i].buffer_length = len;
} else {
((struct st_mysql_bind_50*) b)[i].buffer = block;
((struct st_mysql_bind_50*) b)[i].buffer_length = len;
}
return block;
}
/*
*-----------------------------------------------------------------------------
*
* MysqlBindFreeBuffer --
*
* Frees trhe buffer in a MYSQL_BIND object
*
* Results:
* None.
*
* Side effects:
* Buffer is returned to the system.
*
*-----------------------------------------------------------------------------
*/
static void
MysqlBindFreeBuffer(
MYSQL_BIND* b, /* Pointer to a binding array */
int i /* Index into the array */
) {
if (mysqlClientVersion >= 50100) {
struct st_mysql_bind_51* bindings = (struct st_mysql_bind_51*) b;
if (bindings[i].buffer) {
ckfree(bindings[i].buffer);
bindings[i].buffer = NULL;
}
bindings[i].buffer_length = 0;
} else {
struct st_mysql_bind_50* bindings = (struct st_mysql_bind_50*) b;
if (bindings[i].buffer) {
ckfree(bindings[i].buffer);
bindings[i].buffer = NULL;
}
bindings[i].buffer_length = 0;
}
}
/*
*-----------------------------------------------------------------------------
*
* MysqlBindGetBufferLength, MysqlBindSetBufferType, MysqlBindGetBufferType,
* MysqlBindSetLength, MysqlBindSetIsNull,
* MysqlBindSetError --
*
* Access the fields of a MYSQL_BIND object
*
*-----------------------------------------------------------------------------
*/
static void*
MysqlBindGetBuffer(
MYSQL_BIND* b, /* Binding array to alter */
int i /* Index in the binding array */
) {
if (mysqlClientVersion >= 50100) {
return ((struct st_mysql_bind_51*) b)[i].buffer;
} else {
return ((struct st_mysql_bind_50*) b)[i].buffer;
}
}
static unsigned long
MysqlBindGetBufferLength(
MYSQL_BIND* b, /* Binding array to alter */
int i /* Index in the binding array */
) {
if (mysqlClientVersion >= 50100) {
return ((struct st_mysql_bind_51*) b)[i].buffer_length;
} else {
return ((struct st_mysql_bind_50*) b)[i].buffer_length;
}
}
static enum enum_field_types
MysqlBindGetBufferType(
MYSQL_BIND* b, /* Binding array to alter */
int i /* Index in the binding array */
) {
if (mysqlClientVersion >= 50100) {
return ((struct st_mysql_bind_51*) b)[i].buffer_type;
} else {
return ((struct st_mysql_bind_50*) b)[i].buffer_type;
}
}
static void
MysqlBindSetBufferType(
MYSQL_BIND* b, /* Binding array to alter */
int i, /* Index in the binding array */
enum enum_field_types t /* Buffer type to assign */
) {
if (mysqlClientVersion >= 50100) {
((struct st_mysql_bind_51*) b)[i].buffer_type = t;
} else {
((struct st_mysql_bind_50*) b)[i].buffer_type = t;
}
}
static void
MysqlBindSetLength(
MYSQL_BIND* b, /* Binding array to alter */
int i, /* Index in the binding array */
unsigned long* p /* Length pointer to assign */
) {
if (mysqlClientVersion >= 50100) {
((struct st_mysql_bind_51*) b)[i].length = p;
} else {
((struct st_mysql_bind_50*) b)[i].length = p;
}
}
static void
MysqlBindSetIsNull(
MYSQL_BIND* b, /* Binding array to alter */
int i, /* Index in the binding array */
my_bool* p /* "Is null" indicator pointer to assign */
) {
if (mysqlClientVersion >= 50100) {
((struct st_mysql_bind_51*) b)[i].is_null = p;
} else {
((struct st_mysql_bind_50*) b)[i].is_null = p;
}
}
static void
MysqlBindSetError(
MYSQL_BIND* b, /* Binding array to alter */
int i, /* Index in the binding array */
my_bool* p /* Error indicator pointer to assign */
) {
if (mysqlClientVersion >= 50100) {
((struct st_mysql_bind_51*) b)[i].error = p;
} else {
((struct st_mysql_bind_50*) b)[i].error = p;
}
}
/*
*-----------------------------------------------------------------------------
*
* MysqlFieldIndex --
*
* Return a pointer to a given MYSQL_FIELD structure in an array
*
* The MYSQL_FIELD structure grows by one pointer between 5.0 and 5.1.
* Our code never creates a MYSQL_FIELD, nor does it try to access that
* pointer, so we handle things simply by casting the types.
*
*-----------------------------------------------------------------------------
*/
static MYSQL_FIELD*
MysqlFieldIndex(MYSQL_FIELD* fields,
/* Pointer to the array*/
int i) /* Index in the array */
{
MYSQL_FIELD* retval;
if (mysqlClientVersion >= 50100) {
retval = (MYSQL_FIELD*)(((struct st_mysql_field_51*) fields)+i);
} else {
retval = (MYSQL_FIELD*)(((struct st_mysql_field_50*) fields)+i);
}
return retval;
}
/*
*-----------------------------------------------------------------------------
*
* TransferMysqlError --
*
* Obtains the error message, SQL state, and error number from the
* MySQL client library and transfers them into the Tcl interpreter
*
* Results:
* None.
*
* Side effects:
* Sets the interpreter result and error code to describe the SQL error
*
*-----------------------------------------------------------------------------
*/
static void
TransferMysqlError(
Tcl_Interp* interp, /* Tcl interpreter */
MYSQL* mysqlPtr /* MySQL connection handle */
) {
const char* sqlstate = mysql_sqlstate(mysqlPtr);
Tcl_Obj* errorCode = Tcl_NewObj();
Tcl_ListObjAppendElement(NULL, errorCode, Tcl_NewStringObj("TDBC", -1));
Tcl_ListObjAppendElement(NULL, errorCode,
Tcl_NewStringObj(Tdbc_MapSqlState(sqlstate), -1));
Tcl_ListObjAppendElement(NULL, errorCode,
Tcl_NewStringObj(sqlstate, -1));
Tcl_ListObjAppendElement(NULL, errorCode, Tcl_NewStringObj("MYSQL", -1));
Tcl_ListObjAppendElement(NULL, errorCode,
Tcl_NewWideIntObj(mysql_errno(mysqlPtr)));
Tcl_SetObjErrorCode(interp, errorCode);
Tcl_SetObjResult(interp, Tcl_NewStringObj(mysql_error(mysqlPtr), -1));
}
/*
*-----------------------------------------------------------------------------
*
* TransferMysqlStmtError --
*
* Obtains the error message, SQL state, and error number from the
* MySQL client library and transfers them into the Tcl interpreter
*
* Results:
* None.
*
* Side effects:
* Sets the interpreter result and error code to describe the SQL error
*
*-----------------------------------------------------------------------------
*/
static void
TransferMysqlStmtError(
Tcl_Interp* interp, /* Tcl interpreter */
MYSQL_STMT* stmtPtr /* MySQL statment handle */
) {
const char* sqlstate = mysql_stmt_sqlstate(stmtPtr);
Tcl_Obj* errorCode = Tcl_NewObj();
Tcl_ListObjAppendElement(NULL, errorCode, Tcl_NewStringObj("TDBC", -1));
Tcl_ListObjAppendElement(NULL, errorCode,
Tcl_NewStringObj(Tdbc_MapSqlState(sqlstate), -1));
Tcl_ListObjAppendElement(NULL, errorCode,
Tcl_NewStringObj(sqlstate, -1));
Tcl_ListObjAppendElement(NULL, errorCode, Tcl_NewStringObj("MYSQL", -1));
Tcl_ListObjAppendElement(NULL, errorCode,
Tcl_NewWideIntObj(mysql_stmt_errno(stmtPtr)));
Tcl_SetObjErrorCode(interp, errorCode);
Tcl_SetObjResult(interp, Tcl_NewStringObj(mysql_stmt_error(stmtPtr), -1));
}
/*
*-----------------------------------------------------------------------------
*
* QueryConnectionOption --
*
* Determine the current value of a connection option.
*
* Results:
* Returns a Tcl object containing the value if successful, or NULL
* if unsuccessful. If unsuccessful, stores error information in the
* Tcl interpreter.
*
*-----------------------------------------------------------------------------
*/
static Tcl_Obj*
QueryConnectionOption (
ConnectionData* cdata, /* Connection data */
Tcl_Interp* interp, /* Tcl interpreter */
int optionNum /* Position of the option in the table */
) {
MYSQL_RES* result; /* Result of the MySQL query for the option */
MYSQL_ROW row; /* Row of the result set */
int fieldCount; /* Number of fields in a row */
unsigned long* lengths; /* Character lengths of the fields */
Tcl_Obj* retval; /* Return value */
if (mysql_query(cdata->mysqlPtr, ConnOptions[optionNum].query)) {
TransferMysqlError(interp, cdata->mysqlPtr);
return NULL;
}
result = mysql_store_result(cdata->mysqlPtr);
if (result == NULL) {
TransferMysqlError(interp, cdata->mysqlPtr);
return NULL;
}
fieldCount = mysql_num_fields(result);
if (fieldCount < 2) {
retval = cdata->pidata->literals[LIT_EMPTY];
} else {
if ((row = mysql_fetch_row(result)) == NULL) {
if (mysql_errno(cdata->mysqlPtr)) {
TransferMysqlError(interp, cdata->mysqlPtr);
mysql_free_result(result);
return NULL;
} else {
retval = cdata->pidata->literals[LIT_EMPTY];
}
} else {
lengths = mysql_fetch_lengths(result);
retval = Tcl_NewStringObj(row[1], lengths[1]);
}
}
mysql_free_result(result);
return retval;
}
/*
*-----------------------------------------------------------------------------
*
* ConfigureConnection --
*
* Applies configuration settings to a MySQL connection.
*
* Results:
* Returns a Tcl result. If the result is TCL_ERROR, error information
* is stored in the interpreter.
*
* Side effects:
* Updates configuration in the connection data. Opens a connection
* if none is yet open.
*
*-----------------------------------------------------------------------------
*/
static int
ConfigureConnection(
ConnectionData* cdata, /* Connection data */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
Tcl_Obj* const objv[], /* Parameter data */
int skip /* Number of parameters to skip */
) {
const char* stringOpts[INDX_MAX];
/* String-valued options */
unsigned long mysqlFlags=0; /* Connection flags */
int sslFlag = 0; /* Flag==1 if SSL configuration is needed */
int optionIndex; /* Index of the current option in ConnOptions */
int optionValue; /* Integer value of the current option */
unsigned short port = 0; /* Server port number */
int isolation = ISOL_NONE; /* Isolation level */
int timeout = 0; /* Timeout value */
int i;
Tcl_Obj* retval;
Tcl_Obj* optval;
if (cdata->mysqlPtr != NULL) {
/* Query configuration options on an existing connection */
if (objc == skip) {
retval = Tcl_NewObj();
for (i = 0; ConnOptions[i].name != NULL; ++i) {
if (ConnOptions[i].flags & CONN_OPT_FLAG_ALIAS) continue;
optval = QueryConnectionOption(cdata, interp, i);
if (optval == NULL) {
return TCL_ERROR;
}
Tcl_DictObjPut(NULL, retval,
Tcl_NewStringObj(ConnOptions[i].name, -1),
optval);
}
Tcl_SetObjResult(interp, retval);
return TCL_OK;
} else if (objc == skip+1) {
if (Tcl_GetIndexFromObjStruct(interp, objv[skip],
(void*) ConnOptions,
sizeof(ConnOptions[0]), "option",
0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
retval = QueryConnectionOption(cdata, interp, optionIndex);
if (retval == NULL) {
return TCL_ERROR;
} else {
Tcl_SetObjResult(interp, retval);
return TCL_OK;
}
}
}
if ((objc-skip) % 2 != 0) {
Tcl_WrongNumArgs(interp, skip, objv, "?-option value?...");
return TCL_ERROR;
}
/* Extract options from the command line */
for (i = 0; i < INDX_MAX; ++i) {
stringOpts[i] = NULL;
}
for (i = skip; i < objc; i += 2) {
/* Unknown option */
if (Tcl_GetIndexFromObjStruct(interp, objv[i], (void*) ConnOptions,
sizeof(ConnOptions[0]), "option",
0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
/* Unmodifiable option */
if (cdata->mysqlPtr != NULL && !(ConnOptions[optionIndex].flags
& CONN_OPT_FLAG_MOD)) {
Tcl_Obj* msg = Tcl_NewStringObj("\"", -1);
Tcl_AppendObjToObj(msg, objv[i]);
Tcl_AppendToObj(msg, "\" option cannot be changed dynamically", -1);
Tcl_SetObjResult(interp, msg);
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY000",
"MYSQL", "-1", NULL);
return TCL_ERROR;
}
/* Record option value */
switch (ConnOptions[optionIndex].type) {
case TYPE_STRING:
stringOpts[ConnOptions[optionIndex].info] =
Tcl_GetString(objv[i+1]);
break;
case TYPE_FLAG:
if (Tcl_GetBooleanFromObj(interp, objv[i+1], &optionValue)
!= TCL_OK) {
return TCL_ERROR;
}
if (optionValue) {
mysqlFlags |= ConnOptions[optionIndex].info;
}
break;
case TYPE_ENCODING:
if (strcmp(Tcl_GetString(objv[i+1]), "utf-8")) {
Tcl_SetObjResult(interp,
Tcl_NewStringObj("Only UTF-8 transfer "
"encoding is supported.\n",
-1));
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY000",
"MYSQL", "-1", NULL);
return TCL_ERROR;
}
break;
case TYPE_ISOLATION:
if (Tcl_GetIndexFromObjStruct(interp, objv[i+1], TclIsolationLevels,
sizeof(char *), "isolation level", TCL_EXACT, &isolation)
!= TCL_OK) {
return TCL_ERROR;
}
break;
case TYPE_PORT:
if (Tcl_GetIntFromObj(interp, objv[i+1], &optionValue) != TCL_OK) {
return TCL_ERROR;
}
if (optionValue < 0 || optionValue > 0xffff) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("port number must "
"be in range "
"[0..65535]", -1));
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY000",
"MYSQL", "-1", NULL);
return TCL_ERROR;
}
port = optionValue;
break;
case TYPE_READONLY:
if (Tcl_GetBooleanFromObj(interp, objv[i+1], &optionValue)
!= TCL_OK) {
return TCL_ERROR;
}
if (optionValue != 0) {
Tcl_SetObjResult(interp,
Tcl_NewStringObj("MySQL does not support "
"readonly connections", -1));
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY000",
"MYSQL", "-1", NULL);
return TCL_ERROR;
}
break;
case TYPE_TIMEOUT:
if (Tcl_GetIntFromObj(interp, objv[i+1], &timeout) != TCL_OK) {
return TCL_ERROR;
}
break;
}
if (ConnOptions[optionIndex].flags & CONN_OPT_FLAG_SSL) {
sslFlag = 1;
}
}
if (cdata->mysqlPtr == NULL) {
/* Configuring a new connection. Open the database */
cdata->mysqlPtr = mysql_init(NULL);
if (cdata->mysqlPtr == NULL) {
Tcl_SetObjResult(interp,
Tcl_NewStringObj("mysql_init() failed.", -1));
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY001",
"MYSQL", "NULL", NULL);
return TCL_ERROR;
}
/* Set character set for the connection */
mysql_options(cdata->mysqlPtr, MYSQL_SET_CHARSET_NAME, "utf8");
/* Set SSL options if needed */
if (sslFlag) {
mysql_ssl_set(cdata->mysqlPtr, stringOpts[INDX_SSLKEY],
stringOpts[INDX_SSLCERT], stringOpts[INDX_SSLCA],
stringOpts[INDX_SSLCAPATH],
stringOpts[INDX_SSLCIPHER]);
}
/* Establish the connection */
/*
* TODO - mutex around this unless linked to libmysqlclient_r ?
*/
if (mysql_real_connect(cdata->mysqlPtr, stringOpts[INDX_HOST],
stringOpts[INDX_USER], stringOpts[INDX_PASSWD],
stringOpts[INDX_DB], port,
stringOpts[INDX_SOCKET], mysqlFlags) == NULL) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
cdata->flags |= CONN_FLAG_AUTOCOMMIT;
} else {
/* Already open connection */
if (stringOpts[INDX_USER] != NULL) {
/* User name changed - log in again */
if (mysql_change_user(cdata->mysqlPtr,
stringOpts[INDX_USER],
stringOpts[INDX_PASSWD],
stringOpts[INDX_DB])) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
} else if (stringOpts[INDX_DB] != NULL) {
/* Database name changed - use the new database */
if (mysql_select_db(cdata->mysqlPtr, stringOpts[INDX_DB])) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
}
}
/* Transaction isolation level */
if (isolation != ISOL_NONE) {
if (mysql_query(cdata->mysqlPtr, SqlIsolationLevels[isolation])) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
}
/* Timeout */
if (timeout != 0) {
int result;
Tcl_Obj* query = Tcl_ObjPrintf("SET SESSION WAIT_TIMEOUT = %d\n",
timeout);
Tcl_IncrRefCount(query);
result = mysql_query(cdata->mysqlPtr, Tcl_GetString(query));
Tcl_DecrRefCount(query);
if (result) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionConstructor --
*
* Constructor for ::tdbc::mysql::connection, which represents a
* database connection.
*
* Results:
* Returns a standard Tcl result.
*
* The ConnectionInitMethod takes alternating keywords and values giving
* the configuration parameters of the connection, and attempts to connect
* to the database.
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionConstructor(
ClientData clientData, /* Environment handle */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
PerInterpData* pidata = (PerInterpData*) clientData;
/* Per-interp data for the MYSQL package */
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current object */
int skip = Tcl_ObjectContextSkippedArgs(context);
/* The number of leading arguments to skip */
ConnectionData* cdata; /* Per-connection data */
/* Hang client data on this connection */
cdata = (ConnectionData*) ckalloc(sizeof(ConnectionData));
cdata->refCount = 1;
cdata->pidata = pidata;
cdata->mysqlPtr = NULL;
cdata->nCollations = 0;
cdata->collationSizes = NULL;
cdata->flags = 0;
IncrPerInterpRefCount(pidata);
Tcl_ObjectSetMetadata(thisObject, &connectionDataType, (ClientData) cdata);
/* Configure the connection */
if (ConfigureConnection(cdata, interp, objc, objv, skip) != TCL_OK) {
return TCL_ERROR;
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionBegintransactionMethod --
*
* Method that requests that following operations on an OBBC connection
* be executed as an atomic transaction.
*
* Usage:
* $connection begintransaction
*
* Parameters:
* None.
*
* Results:
* Returns an empty result if successful, and throws an error otherwise.
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionBegintransactionMethod(
ClientData dummy, /* Unused */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
(void)dummy;
/* Check parameters */
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
/* Reject attempts at nested transactions */
if (cdata->flags & CONN_FLAG_IN_XCN) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("MySQL does not support "
"nested transactions", -1));
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HYC00",
"MYSQL", "-1", NULL);
return TCL_ERROR;
}
cdata->flags |= CONN_FLAG_IN_XCN;
/* Turn off autocommit for the duration of the transaction */
if (cdata->flags & CONN_FLAG_AUTOCOMMIT) {
if (mysql_autocommit(cdata->mysqlPtr, 0)) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
cdata->flags &= ~CONN_FLAG_AUTOCOMMIT;
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionColumnsMethod --
*
* Method that asks for the names of columns in a table
* in the database (optionally matching a given pattern)
*
* Usage:
* $connection columns table ?pattern?
*
* Parameters:
* None.
*
* Results:
* Returns the list of tables
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionColumnsMethod(
ClientData dummy, /* Completion type */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
/* Instance data */
PerInterpData* pidata = cdata->pidata;
/* Per-interpreter data */
Tcl_Obj** literals = pidata->literals;
/* Literal pool */
const char* patternStr; /* Pattern to match table names */
MYSQL_RES* results; /* Result set */
Tcl_Obj* retval; /* List of table names */
Tcl_Obj* name; /* Name of a column */
Tcl_Obj* attrs; /* Attributes of the column */
Tcl_HashEntry* entry; /* Hash entry for data type */
(void)dummy;
/* Check parameters */
if (objc == 3) {
patternStr = NULL;
} else if (objc == 4) {
patternStr = Tcl_GetString(objv[3]);
} else {
Tcl_WrongNumArgs(interp, 2, objv, "table ?pattern?");
return TCL_ERROR;
}
results = mysql_list_fields(cdata->mysqlPtr, Tcl_GetString(objv[2]),
patternStr);
if (results == NULL) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
} else {
unsigned int fieldCount = mysql_num_fields(results);
MYSQL_FIELD* fields = mysql_fetch_fields(results);
unsigned int i;
retval = Tcl_NewObj();
Tcl_IncrRefCount(retval);
for (i = 0; i < fieldCount; ++i) {
MYSQL_FIELD* field = MysqlFieldIndex(fields, i);
attrs = Tcl_NewObj();
name = Tcl_NewStringObj(field->name, field->name_length);
Tcl_DictObjPut(NULL, attrs, literals[LIT_NAME], name);
/* TODO - Distinguish CHAR and BINARY */
entry = Tcl_FindHashEntry(&(pidata->typeNumHash),
(char*) field->type);
if (entry != NULL) {
Tcl_DictObjPut(NULL, attrs, literals[LIT_TYPE],
(Tcl_Obj*) Tcl_GetHashValue(entry));
}
if (IS_NUM(field->type)) {
Tcl_DictObjPut(NULL, attrs, literals[LIT_PRECISION],
Tcl_NewWideIntObj(field->length));
} else if (field->charsetnr < cdata->nCollations) {
Tcl_DictObjPut(NULL, attrs, literals[LIT_PRECISION],
Tcl_NewWideIntObj(field->length
/ cdata->collationSizes[field->charsetnr]));
}
Tcl_DictObjPut(NULL, attrs, literals[LIT_SCALE],
Tcl_NewWideIntObj(field->decimals));
Tcl_DictObjPut(NULL, attrs, literals[LIT_NULLABLE],
Tcl_NewWideIntObj(!(field->flags
& (NOT_NULL_FLAG))));
Tcl_DictObjPut(NULL, retval, name, attrs);
}
mysql_free_result(results);
Tcl_SetObjResult(interp, retval);
Tcl_DecrRefCount(retval);
return TCL_OK;
}
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionCommitMethod --
*
* Method that requests that a pending transaction against a database
* be committed.
*
* Usage:
* $connection commit
*
* Parameters:
* None.
*
* Results:
* Returns an empty Tcl result if successful, and throws an error
* otherwise.
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionCommitMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
/* Instance data */
my_bool rc; /* MySQL status return */
(void)dummy;
/* Check parameters */
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
/* Reject the request if no transaction is in progress */
if (!(cdata->flags & CONN_FLAG_IN_XCN)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("no transaction is in "
"progress", -1));
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY010",
"MYSQL", "-1", NULL);
return TCL_ERROR;
}
/* End transaction, turn off "transaction in progress", and report status */
rc = mysql_commit(cdata->mysqlPtr);
cdata->flags &= ~ CONN_FLAG_IN_XCN;
if (rc) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionConfigureMethod --
*
* Change configuration parameters on an open connection.
*
* Usage:
* $connection configure ?-keyword? ?value? ?-keyword value ...?
*
* Parameters:
* Keyword-value pairs (or a single keyword, or an empty set)
* of configuration options.
*
* Options:
* The following options are supported;
* -database
* Name of the database to use by default in queries
* -encoding
* Character encoding to use with the server. (Must be utf-8)
* -isolation
* Transaction isolation level.
* -readonly
* Read-only flag (must be a false Boolean value)
* -timeout
* Timeout value (both wait_timeout and interactive_timeout)
*
* Other options supported by the constructor are here in read-only
* mode; any attempt to change them will result in an error.
*
*-----------------------------------------------------------------------------
*/
static int ConnectionConfigureMethod(
ClientData dummy,
Tcl_Interp* interp,
Tcl_ObjectContext objectContext,
int objc,
Tcl_Obj *const objv[]
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
int skip = Tcl_ObjectContextSkippedArgs(objectContext);
/* Number of arguments to skip */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
(void)dummy;
/* Instance data */
return ConfigureConnection(cdata, interp, objc, objv, skip);
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionEvaldirectMethod --
*
* Evaluates a MySQL statement that is not supported by the prepared
* statement API.
*
* Usage:
* $connection evaldirect sql-statement
*
* Parameters:
* sql-statement -
* SQL statement to evaluate. The statement may not contain
* substitutions.
*
* Results:
* Returns a standard Tcl result. If the operation is successful,
* the result consists of a list of rows (in the same form as
* [$connection allrows -as dicts]). If the operation fails, the
* result is an error message.
*
* Side effects:
* Whatever the SQL statement does.
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionEvaldirectMethod(
ClientData dummy, /* Unused */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[]) /* Parameter vector */
{
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* Current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
/* Instance data */
int nColumns; /* Number of columns in the result set */
MYSQL_RES* resultPtr; /* MySQL result set */
MYSQL_ROW rowPtr; /* One row of the result set */
unsigned long* lengths; /* Lengths of the fields in a row */
Tcl_Obj* retObj; /* Result set as a Tcl list */
Tcl_Obj* rowObj; /* One row of the result set as a Tcl list */
Tcl_Obj* fieldObj; /* One field of the row */
int i;
(void)dummy;
/* Check parameters */
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
/* Execute the given statement */
if (mysql_query(cdata->mysqlPtr, Tcl_GetString(objv[2]))) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
/* Retrieve the result set */
resultPtr = mysql_store_result(cdata->mysqlPtr);
nColumns = mysql_field_count(cdata->mysqlPtr);
if (resultPtr == NULL) {
/*
* Can't retrieve result set. Distinguish result-less statements
* from MySQL errors.
*/
if (nColumns == 0) {
Tcl_SetObjResult
(interp,
Tcl_NewWideIntObj(mysql_affected_rows(cdata->mysqlPtr)));
return TCL_OK;
} else {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
}
/* Make a list-of-lists of the result */
retObj = Tcl_NewObj();
while ((rowPtr = mysql_fetch_row(resultPtr)) != NULL) {
rowObj = Tcl_NewObj();
lengths = mysql_fetch_lengths(resultPtr);
for (i = 0; i < nColumns; ++i) {
if (rowPtr[i] != NULL) {
fieldObj = Tcl_NewStringObj(rowPtr[i], lengths[i]);
} else {
fieldObj = cdata->pidata->literals[LIT_EMPTY];
}
Tcl_ListObjAppendElement(NULL, rowObj, fieldObj);
}
Tcl_ListObjAppendElement(NULL, retObj, rowObj);
}
Tcl_SetObjResult(interp, retObj);
/*
* Free the result set.
*/
mysql_free_result(resultPtr);
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionNeedCollationInfoMethod --
*
* Internal method that determines whether the collation lengths
* are known yet.
*
* Usage:
* $connection NeedCollationInfo
*
* Parameters:
* None.
*
* Results:
* Returns a Boolean value.
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionNeedCollationInfoMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
/* Instance data */
(void)dummy;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(cdata->collationSizes == NULL));
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionRollbackMethod --
*
* Method that requests that a pending transaction against a database
* be rolled back.
*
* Usage:
* $connection rollback
*
* Parameters:
* None.
*
* Results:
* Returns an empty Tcl result if successful, and throws an error
* otherwise.
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionRollbackMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
/* Instance data */
my_bool rc; /* Result code from MySQL operations */
(void)dummy;
/* Check parameters */
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
/* Reject the request if no transaction is in progress */
if (!(cdata->flags & CONN_FLAG_IN_XCN)) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("no transaction is in "
"progress", -1));
Tcl_SetErrorCode(interp, "TDBC", "GENERAL_ERROR", "HY010",
"MYSQL", "-1", NULL);
return TCL_ERROR;
}
/* End transaction, turn off "transaction in progress", and report status */
rc = mysql_rollback(cdata->mysqlPtr);
cdata->flags &= ~CONN_FLAG_IN_XCN;
if (rc) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionSetCollationInfoMethod --
*
* Internal method that saves the character lengths of the collations
*
* Usage:
* $connection SetCollationInfo {collationNum size} ...
*
* Parameters:
* One or more pairs of collation number and character length,
* ordered in decreasing sequence by collation number.
*
* Results:
* None.
*
* The [$connection columns $table] method needs to know the sizes
* of characters in a given column's collation and character set.
* This information is available by querying INFORMATION_SCHEMA, which
* is easier to do from Tcl than C. This method passes in the results.
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionSetCollationInfoMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
/* Instance data */
int listLen;
Tcl_Obj* objPtr;
unsigned int collationNum;
int i;
int t;
(void)dummy;
if (objc <= 2) {
Tcl_WrongNumArgs(interp, 2, objv, "{collationNum size}...");
return TCL_ERROR;
}
if (Tcl_ListObjIndex(interp, objv[2], 0, &objPtr) != TCL_OK) {
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objPtr, &t) != TCL_OK) {
return TCL_ERROR;
}
cdata->nCollations = (unsigned int)(t+1);
if (cdata->collationSizes) {
ckfree((char*) cdata->collationSizes);
}
cdata->collationSizes =
(int*) ckalloc(cdata->nCollations * sizeof(int));
memset(cdata->collationSizes, 0, cdata->nCollations * sizeof(int));
for (i = 2; i < objc; ++i) {
if (Tcl_ListObjLength(interp, objv[i], &listLen) != TCL_OK) {
return TCL_ERROR;
}
if (listLen != 2) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("args must be 2-element "
"lists", -1));
return TCL_ERROR;
}
if (Tcl_ListObjIndex(interp, objv[i], 0, &objPtr) != TCL_OK
|| Tcl_GetIntFromObj(interp, objPtr, &t) != TCL_OK) {
return TCL_ERROR;
}
collationNum = (unsigned int) t;
if (collationNum > cdata->nCollations) {
Tcl_SetObjResult(interp, Tcl_NewStringObj("collations must be "
"in decreasing sequence",
-1));
return TCL_ERROR;
}
if ((Tcl_ListObjIndex(interp, objv[i], 1, &objPtr) != TCL_OK)
|| (Tcl_GetIntFromObj(interp, objPtr,
cdata->collationSizes+collationNum)
!= TCL_OK)) {
return TCL_ERROR;
}
}
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ConnectionTablesMethod --
*
* Method that asks for the names of tables in the database (optionally
* matching a given pattern
*
* Usage:
* $connection tables ?pattern?
*
* Parameters:
* None.
*
* Results:
* Returns the list of tables
*
*-----------------------------------------------------------------------------
*/
static int
ConnectionTablesMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext objectContext, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(objectContext);
/* The current connection object */
ConnectionData* cdata = (ConnectionData*)
Tcl_ObjectGetMetadata(thisObject, &connectionDataType);
/* Instance data */
Tcl_Obj** literals = cdata->pidata->literals;
/* Literal pool */
const char* patternStr = NULL;
/* Pattern to match table names */
MYSQL_RES* results = NULL; /* Result set */
MYSQL_ROW row = NULL; /* Row in the result set */
int status = TCL_OK; /* Return status */
Tcl_Obj* retval = NULL; /* List of table names */
(void)dummy;
/* Check parameters */
if (objc == 2) {
patternStr = NULL;
} else if (objc == 3) {
patternStr = Tcl_GetString(objv[2]);
} else {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
results = mysql_list_tables(cdata->mysqlPtr, patternStr);
if (results == NULL) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
} else {
retval = Tcl_NewObj();
Tcl_IncrRefCount(retval);
while ((row = mysql_fetch_row(results)) != NULL) {
unsigned long * lengths = mysql_fetch_lengths(results);
if (row[0]) {
Tcl_ListObjAppendElement(NULL, retval,
Tcl_NewStringObj(row[0],
(int)lengths[0]));
Tcl_ListObjAppendElement(NULL, retval, literals[LIT_EMPTY]);
}
}
if (mysql_errno(cdata->mysqlPtr)) {
TransferMysqlError(interp, cdata->mysqlPtr);
status = TCL_ERROR;
}
if (status == TCL_OK) {
Tcl_SetObjResult(interp, retval);
}
Tcl_DecrRefCount(retval);
mysql_free_result(results);
return status;
}
}
/*
*-----------------------------------------------------------------------------
*
* DeleteCmd --
*
* Callback executed when the initialization method of the connection
* class is deleted.
*
* Side effects:
* Dismisses the environment, which has the effect of shutting
* down MYSQL when it is no longer required.
*
*-----------------------------------------------------------------------------
*/
static void
DeleteCmd (
ClientData clientData /* Environment handle */
) {
PerInterpData* pidata = (PerInterpData*) clientData;
DecrPerInterpRefCount(pidata);
}
/*
*-----------------------------------------------------------------------------
*
* CloneCmd --
*
* Callback executed when any of the MYSQL client methods is cloned.
*
* Results:
* Returns TCL_OK to allow the method to be copied.
*
* Side effects:
* Obtains a fresh copy of the environment handle, to keep the
* refcounts accurate
*
*-----------------------------------------------------------------------------
*/
static int
CloneCmd(
Tcl_Interp* dummy, /* Tcl interpreter */
ClientData oldClientData, /* Environment handle to be discarded */
ClientData* newClientData /* New environment handle to be used */
) {
(void)dummy;
*newClientData = oldClientData;
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* DeleteConnectionMetadata, DeleteConnection --
*
* Cleans up when a database connection is deleted.
*
* Results:
* None.
*
* Side effects:
* Terminates the connection and frees all system resources associated
* with it.
*
*-----------------------------------------------------------------------------
*/
static void
DeleteConnectionMetadata(
ClientData clientData /* Instance data for the connection */
) {
DecrConnectionRefCount((ConnectionData*)clientData);
}
static void
DeleteConnection(
ConnectionData* cdata /* Instance data for the connection */
) {
if (cdata->collationSizes != NULL) {
ckfree((char*) cdata->collationSizes);
}
if (cdata->mysqlPtr != NULL) {
mysql_close(cdata->mysqlPtr);
}
DecrPerInterpRefCount(cdata->pidata);
ckfree((char*) cdata);
}
/*
*-----------------------------------------------------------------------------
*
* CloneConnection --
*
* Attempts to clone an MYSQL connection's metadata.
*
* Results:
* Returns the new metadata
*
* At present, we don't attempt to clone connections - it's not obvious
* that such an action would ever even make sense. Instead, we return NULL
* to indicate that the metadata should not be cloned. (Note that this
* action isn't right, either. What *is* right is to indicate that the object
* is not clonable, but the API gives us no way to do that.
*
*-----------------------------------------------------------------------------
*/
static int
CloneConnection(
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
ClientData metadata, /* Metadata to be cloned */
ClientData* newMetaData /* Where to put the cloned metadata */
) {
(void)metadata;
(void)newMetaData;
Tcl_SetObjResult(interp,
Tcl_NewStringObj("MYSQL connections are not clonable", -1));
return TCL_ERROR;
}
/*
*-----------------------------------------------------------------------------
*
* NewStatement --
*
* Creates an empty object to hold statement data.
*
* Results:
* Returns a pointer to the newly-created object.
*
*-----------------------------------------------------------------------------
*/
static StatementData*
NewStatement(
ConnectionData* cdata /* Instance data for the connection */
) {
StatementData* sdata = (StatementData*) ckalloc(sizeof(StatementData));
sdata->refCount = 1;
sdata->cdata = cdata;
IncrConnectionRefCount(cdata);
sdata->subVars = Tcl_NewObj();
Tcl_IncrRefCount(sdata->subVars);
sdata->params = NULL;
sdata->nativeSql = NULL;
sdata->stmtPtr = NULL;
sdata->metadataPtr = NULL;
sdata->columnNames = NULL;
sdata->flags = 0;
return sdata;
}
/*
*-----------------------------------------------------------------------------
*
* AllocAndPrepareStatement --
*
* Allocate space for a MySQL prepared statement, and prepare the
* statement.
*
* Results:
* Returns the statement handle if successful, and NULL on failure.
*
* Side effects:
* Prepares the statement.
* Stores error message and error code in the interpreter on failure.
*
*-----------------------------------------------------------------------------
*/
static MYSQL_STMT*
AllocAndPrepareStatement(
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
StatementData* sdata /* Statement data */
) {
ConnectionData* cdata = sdata->cdata;
/* Connection data */
MYSQL_STMT* stmtPtr; /* Statement handle */
const char* nativeSqlStr; /* Native SQL statement to prepare */
int nativeSqlLen; /* Length of the statement */
/* Allocate space for the prepared statement */
stmtPtr = mysql_stmt_init(cdata->mysqlPtr);
/*
* MySQL allows only one writable cursor open at a time, and
* the default cursor type is writable. Make all our cursors
* read-only to avoid 'Commands out of sync' errors.
*/
if (stmtPtr == NULL) {
TransferMysqlError(interp, cdata->mysqlPtr);
} else {
/* Prepare the statement */
nativeSqlStr = Tcl_GetStringFromObj(sdata->nativeSql, &nativeSqlLen);
if (mysql_stmt_prepare(stmtPtr, nativeSqlStr, nativeSqlLen)) {
TransferMysqlStmtError(interp, stmtPtr);
mysql_stmt_close(stmtPtr);
stmtPtr = NULL;
}
}
return stmtPtr;
}
/*
*-----------------------------------------------------------------------------
*
* ResultDescToTcl --
*
* Converts a MySQL result description for return as a Tcl list.
*
* Results:
* Returns a Tcl object holding the result description
*
* If any column names are duplicated, they are disambiguated by
* appending '#n' where n increments once for each occurrence of the
* column name.
*
*-----------------------------------------------------------------------------
*/
static Tcl_Obj*
ResultDescToTcl(
MYSQL_RES* result, /* Result set description */
int flags /* Flags governing the conversion */
) {
Tcl_Obj* retval = Tcl_NewObj();
Tcl_HashTable names; /* Hash table to resolve name collisions */
Tcl_Obj* nameObj; /* Name of a result column */
int isNew; /* Flag == 1 if a result column is unique */
Tcl_HashEntry* entry; /* Hash table entry for a column name */
int count; /* Number used to disambiguate a column name */
(void)flags;
Tcl_InitHashTable(&names, TCL_STRING_KEYS);
if (result != NULL) {
unsigned int fieldCount = mysql_num_fields(result);
MYSQL_FIELD* fields = mysql_fetch_fields(result);
unsigned int i;
char numbuf[16];
for (i = 0; i < fieldCount; ++i) {
MYSQL_FIELD* field = MysqlFieldIndex(fields, i);
nameObj = Tcl_NewStringObj(field->name, field->name_length);
Tcl_IncrRefCount(nameObj);
entry = Tcl_CreateHashEntry(&names, field->name, &isNew);
count = 1;
while (!isNew) {
count = PTR2INT(Tcl_GetHashValue(entry));
++count;
Tcl_SetHashValue(entry, INT2PTR(count));
sprintf(numbuf, "#%d", count);
Tcl_AppendToObj(nameObj, numbuf, -1);
entry = Tcl_CreateHashEntry(&names, Tcl_GetString(nameObj),
&isNew);
}
Tcl_SetHashValue(entry, INT2PTR(count));
Tcl_ListObjAppendElement(NULL, retval, nameObj);
Tcl_DecrRefCount(nameObj);
}
}
Tcl_DeleteHashTable(&names);
return retval;
}
/*
*-----------------------------------------------------------------------------
*
* StatementConstructor --
*
* C-level initialization for the object representing an MySQL prepared
* statement.
*
* Usage:
* statement new connection statementText
* statement create name connection statementText
*
* Parameters:
* connection -- the MySQL connection object
* statementText -- text of the statement to prepare.
*
* Results:
* Returns a standard Tcl result
*
* Side effects:
* Prepares the statement, and stores it (plus a reference to the
* connection) in instance metadata.
*
*-----------------------------------------------------------------------------
*/
static int
StatementConstructor(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current statement object */
int skip = Tcl_ObjectContextSkippedArgs(context);
/* Number of args to skip before the
* payload arguments */
Tcl_Object connectionObject;
/* The database connection as a Tcl_Object */
ConnectionData* cdata; /* The connection object's data */
StatementData* sdata; /* The statement's object data */
Tcl_Obj* tokens; /* The tokens of the statement to be prepared */
int tokenc; /* Length of the 'tokens' list */
Tcl_Obj** tokenv; /* Exploded tokens from the list */
Tcl_Obj* nativeSql; /* SQL statement mapped to native form */
char* tokenStr; /* Token string */
int tokenLen; /* Length of a token */
int nParams; /* Number of parameters of the statement */
int i;
(void)dummy;
/* Find the connection object, and get its data. */
thisObject = Tcl_ObjectContextObject(context);
if (objc != skip+2) {
Tcl_WrongNumArgs(interp, skip, objv, "connection statementText");
return TCL_ERROR;
}
connectionObject = Tcl_GetObjectFromObj(interp, objv[skip]);
if (connectionObject == NULL) {
return TCL_ERROR;
}
cdata = (ConnectionData*) Tcl_ObjectGetMetadata(connectionObject,
&connectionDataType);
if (cdata == NULL) {
Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
" does not refer to a MySQL connection", NULL);
return TCL_ERROR;
}
/*
* Allocate an object to hold data about this statement
*/
sdata = NewStatement(cdata);
/* Tokenize the statement */
tokens = Tdbc_TokenizeSql(interp, Tcl_GetString(objv[skip+1]));
if (tokens == NULL) {
goto freeSData;
}
Tcl_IncrRefCount(tokens);
/*
* Rewrite the tokenized statement to MySQL syntax. Reject the
* statement if it is actually multiple statements.
*/
if (Tcl_ListObjGetElements(interp, tokens, &tokenc, &tokenv) != TCL_OK) {
goto freeTokens;
}
nativeSql = Tcl_NewObj();
Tcl_IncrRefCount(nativeSql);
for (i = 0; i < tokenc; ++i) {
tokenStr = Tcl_GetStringFromObj(tokenv[i], &tokenLen);
switch (tokenStr[0]) {
case '$':
case ':':
case '@':
Tcl_AppendToObj(nativeSql, "?", 1);
Tcl_ListObjAppendElement(NULL, sdata->subVars,
Tcl_NewStringObj(tokenStr+1, tokenLen-1));
break;
case ';':
Tcl_SetObjResult(interp,
Tcl_NewStringObj("tdbc::mysql"
" does not support semicolons "
"in statements", -1));
goto freeNativeSql;
break;
default:
Tcl_AppendToObj(nativeSql, tokenStr, tokenLen);
break;
}
}
sdata->nativeSql = nativeSql;
Tcl_DecrRefCount(tokens);
/* Prepare the statement */
sdata->stmtPtr = AllocAndPrepareStatement(interp, sdata);
if (sdata->stmtPtr == NULL) {
goto freeSData;
}
/* Get result set metadata */
sdata->metadataPtr = mysql_stmt_result_metadata(sdata->stmtPtr);
if (mysql_stmt_errno(sdata->stmtPtr)) {
TransferMysqlStmtError(interp, sdata->stmtPtr);
goto freeSData;
}
sdata->columnNames = ResultDescToTcl(sdata->metadataPtr, 0);
Tcl_IncrRefCount(sdata->columnNames);
Tcl_ListObjLength(NULL, sdata->subVars, &nParams);
sdata->params = (ParamData*) ckalloc(nParams * sizeof(ParamData));
for (i = 0; i < nParams; ++i) {
sdata->params[i].flags = PARAM_IN;
sdata->params[i].dataType = MYSQL_TYPE_VARCHAR;
sdata->params[i].precision = 0;
sdata->params[i].scale = 0;
}
/* Attach the current statement data as metadata to the current object */
Tcl_ObjectSetMetadata(thisObject, &statementDataType, (ClientData) sdata);
return TCL_OK;
/* On error, unwind all the resource allocations */
freeNativeSql:
Tcl_DecrRefCount(nativeSql);
freeTokens:
Tcl_DecrRefCount(tokens);
freeSData:
DecrStatementRefCount(sdata);
return TCL_ERROR;
}
/*
*-----------------------------------------------------------------------------
*
* StatementParamsMethod --
*
* Lists the parameters in a MySQL statement.
*
* Usage:
* $statement params
*
* Results:
* Returns a standard Tcl result containing a dictionary. The keys
* of the dictionary are parameter names, and the values are parameter
* types, themselves expressed as dictionaries containing the keys,
* 'name', 'direction', 'type', 'precision', 'scale' and 'nullable'.
*
*
*-----------------------------------------------------------------------------
*/
static int
StatementParamsMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current statement object */
StatementData* sdata /* The current statement */
= (StatementData*) Tcl_ObjectGetMetadata(thisObject,
&statementDataType);
ConnectionData* cdata = sdata->cdata;
PerInterpData* pidata = cdata->pidata; /* Per-interp data */
Tcl_Obj** literals = pidata->literals; /* Literal pool */
int nParams; /* Number of parameters to the statement */
Tcl_Obj* paramName; /* Name of a parameter */
Tcl_Obj* paramDesc; /* Description of one parameter */
Tcl_Obj* dataTypeName; /* Name of a parameter's data type */
Tcl_Obj* retVal; /* Return value from this command */
Tcl_HashEntry* typeHashEntry;
int i;
(void)dummy;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
retVal = Tcl_NewObj();
Tcl_ListObjLength(NULL, sdata->subVars, &nParams);
for (i = 0; i < nParams; ++i) {
paramDesc = Tcl_NewObj();
Tcl_ListObjIndex(NULL, sdata->subVars, i, &paramName);
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_NAME], paramName);
switch (sdata->params[i].flags & (PARAM_IN | PARAM_OUT)) {
case PARAM_IN:
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_DIRECTION],
literals[LIT_IN]);
break;
case PARAM_OUT:
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_DIRECTION],
literals[LIT_OUT]);
break;
case PARAM_IN | PARAM_OUT:
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_DIRECTION],
literals[LIT_INOUT]);
break;
default:
break;
}
typeHashEntry =
Tcl_FindHashEntry(&(pidata->typeNumHash),
INT2PTR(sdata->params[i].dataType));
if (typeHashEntry != NULL) {
dataTypeName = (Tcl_Obj*) Tcl_GetHashValue(typeHashEntry);
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_TYPE], dataTypeName);
}
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_PRECISION],
Tcl_NewWideIntObj(sdata->params[i].precision));
Tcl_DictObjPut(NULL, paramDesc, literals[LIT_SCALE],
Tcl_NewWideIntObj(sdata->params[i].scale));
Tcl_DictObjPut(NULL, retVal, paramName, paramDesc);
}
Tcl_SetObjResult(interp, retVal);
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* StatementParamtypeMethod --
*
* Defines a parameter type in a MySQL statement.
*
* Usage:
* $statement paramtype paramName ?direction? type ?precision ?scale??
*
* Results:
* Returns a standard Tcl result.
*
* Side effects:
* Updates the description of the given parameter.
*
*-----------------------------------------------------------------------------
*/
static int
StatementParamtypeMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current statement object */
StatementData* sdata /* The current statement */
= (StatementData*) Tcl_ObjectGetMetadata(thisObject,
&statementDataType);
static const struct {
const char* name;
int flags;
} directions[] = {
{ "in", PARAM_IN },
{ "out", PARAM_OUT },
{ "inout", PARAM_IN | PARAM_OUT },
{ NULL, 0 }
};
int direction;
int typeNum; /* Data type number of a parameter */
int precision; /* Data precision */
int scale; /* Data scale */
int nParams; /* Number of parameters to the statement */
const char* paramName; /* Name of the parameter being set */
Tcl_Obj* targetNameObj; /* Name of the ith parameter in the statement */
const char* targetName; /* Name of a candidate parameter in the
* statement */
int matchCount = 0; /* Number of parameters matching the name */
Tcl_Obj* errorObj; /* Error message */
int i;
(void)dummy;
/* Check parameters */
if (objc < 4) {
goto wrongNumArgs;
}
i = 3;
if (Tcl_GetIndexFromObjStruct(interp, objv[i], directions,
sizeof(directions[0]), "direction",
TCL_EXACT, &direction) != TCL_OK) {
direction = PARAM_IN;
Tcl_ResetResult(interp);
} else {
++i;
}
if (i >= objc) goto wrongNumArgs;
if (Tcl_GetIndexFromObjStruct(interp, objv[i], dataTypes,
sizeof(dataTypes[0]), "SQL data type",
TCL_EXACT, &typeNum) == TCL_OK) {
++i;
} else {
return TCL_ERROR;
}
if (i < objc) {
if (Tcl_GetIntFromObj(interp, objv[i], &precision) == TCL_OK) {
++i;
} else {
return TCL_ERROR;
}
}
if (i < objc) {
if (Tcl_GetIntFromObj(interp, objv[i], &scale) == TCL_OK) {
++i;
} else {
return TCL_ERROR;
}
}
if (i != objc) {
goto wrongNumArgs;
}
/* Look up parameters by name. */
Tcl_ListObjLength(NULL, sdata->subVars, &nParams);
paramName = Tcl_GetString(objv[2]);
for (i = 0; i < nParams; ++i) {
Tcl_ListObjIndex(NULL, sdata->subVars, i, &targetNameObj);
targetName = Tcl_GetString(targetNameObj);
if (!strcmp(paramName, targetName)) {
++matchCount;
sdata->params[i].flags = direction;
sdata->params[i].dataType = dataTypes[typeNum].num;
sdata->params[i].precision = precision;
sdata->params[i].scale = scale;
}
}
if (matchCount == 0) {
errorObj = Tcl_NewStringObj("unknown parameter \"", -1);
Tcl_AppendToObj(errorObj, paramName, -1);
Tcl_AppendToObj(errorObj, "\": must be ", -1);
for (i = 0; i < nParams; ++i) {
Tcl_ListObjIndex(NULL, sdata->subVars, i, &targetNameObj);
Tcl_AppendObjToObj(errorObj, targetNameObj);
if (i < nParams-2) {
Tcl_AppendToObj(errorObj, ", ", -1);
} else if (i == nParams-2) {
Tcl_AppendToObj(errorObj, " or ", -1);
}
}
Tcl_SetObjResult(interp, errorObj);
return TCL_ERROR;
}
return TCL_OK;
wrongNumArgs:
Tcl_WrongNumArgs(interp, 2, objv,
"name ?direction? type ?precision ?scale??");
return TCL_ERROR;
}
/*
*-----------------------------------------------------------------------------
*
* DeleteStatementMetadata, DeleteStatement --
*
* Cleans up when a MySQL statement is no longer required.
*
* Side effects:
* Frees all resources associated with the statement.
*
*-----------------------------------------------------------------------------
*/
static void
DeleteStatementMetadata(
ClientData clientData /* Instance data for the connection */
) {
DecrStatementRefCount((StatementData*)clientData);
}
static void
DeleteStatement(
StatementData* sdata /* Metadata for the statement */
) {
if (sdata->columnNames != NULL) {
Tcl_DecrRefCount(sdata->columnNames);
}
if (sdata->metadataPtr != NULL) {
mysql_free_result(sdata->metadataPtr);
}
if (sdata->stmtPtr != NULL) {
mysql_stmt_close(sdata->stmtPtr);
}
if (sdata->nativeSql != NULL) {
Tcl_DecrRefCount(sdata->nativeSql);
}
if (sdata->params != NULL) {
ckfree((char*)sdata->params);
}
Tcl_DecrRefCount(sdata->subVars);
DecrConnectionRefCount(sdata->cdata);
ckfree((char*)sdata);
}
/*
*-----------------------------------------------------------------------------
*
* CloneStatement --
*
* Attempts to clone a MySQL statement's metadata.
*
* Results:
* Returns the new metadata
*
* At present, we don't attempt to clone statements - it's not obvious
* that such an action would ever even make sense. Instead, we return NULL
* to indicate that the metadata should not be cloned. (Note that this
* action isn't right, either. What *is* right is to indicate that the object
* is not clonable, but the API gives us no way to do that.
*
*-----------------------------------------------------------------------------
*/
static int
CloneStatement(
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
ClientData metadata, /* Metadata to be cloned */
ClientData* newMetaData /* Where to put the cloned metadata */
) {
(void)metadata;
(void)newMetaData;
Tcl_SetObjResult(interp,
Tcl_NewStringObj("MySQL statements are not clonable", -1));
return TCL_ERROR;
}
/*
*-----------------------------------------------------------------------------
*
* ResultSetConstructor --
*
* Constructs a new result set.
*
* Usage:
* $resultSet new statement ?dictionary?
* $resultSet create name statement ?dictionary?
*
* Parameters:
* statement -- Statement handle to which this resultset belongs
* dictionary -- Dictionary containing the substitutions for named
* parameters in the given statement.
*
* Results:
* Returns a standard Tcl result. On error, the interpreter result
* contains an appropriate message.
*
*-----------------------------------------------------------------------------
*/
static int
ResultSetConstructor(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current result set object */
int skip = Tcl_ObjectContextSkippedArgs(context);
/* Number of args to skip */
Tcl_Object statementObject; /* The current statement object */
ConnectionData* cdata; /* The MySQL connection object's data */
StatementData* sdata; /* The statement object's data */
ResultSetData* rdata; /* THe result set object's data */
int nParams; /* The parameter count on the statement */
int nBound; /* Number of parameters bound so far */
Tcl_Obj* paramNameObj; /* Name of the current parameter */
const char* paramName; /* Name of the current parameter */
Tcl_Obj* paramValObj; /* Value of the current parameter */
const char* paramValStr; /* String value of the current parameter */
char* bufPtr; /* Pointer to the parameter buffer */
int len; /* Length of a bound parameter */
int nColumns; /* Number of columns in the result set */
MYSQL_FIELD* fields = NULL; /* Description of columns of the result set */
MYSQL_BIND* resultBindings; /* Bindings of the columns of the result set */
unsigned long* resultLengths;
/* Lengths of the columns of the result set */
int i;
(void)dummy;
/* Check parameter count */
if (objc != skip+1 && objc != skip+2) {
Tcl_WrongNumArgs(interp, skip, objv, "statement ?dictionary?");
return TCL_ERROR;
}
/* Initialize the base classes */
Tcl_ObjectContextInvokeNext(interp, context, skip, objv, skip);
/* Find the statement object, and get the statement data */
statementObject = Tcl_GetObjectFromObj(interp, objv[skip]);
if (statementObject == NULL) {
return TCL_ERROR;
}
sdata = (StatementData*) Tcl_ObjectGetMetadata(statementObject,
&statementDataType);
if (sdata == NULL) {
Tcl_AppendResult(interp, Tcl_GetString(objv[skip]),
" does not refer to a MySQL statement", NULL);
return TCL_ERROR;
}
Tcl_ListObjLength(NULL, sdata->columnNames, &nColumns);
cdata = sdata->cdata;
/*
* If there is no transaction in progress, turn on auto-commit so that
* this statement will execute directly.
*/
if ((cdata->flags & (CONN_FLAG_IN_XCN | CONN_FLAG_AUTOCOMMIT)) == 0) {
if (mysql_autocommit(cdata->mysqlPtr, 1)) {
TransferMysqlError(interp, cdata->mysqlPtr);
return TCL_ERROR;
}
cdata->flags |= CONN_FLAG_AUTOCOMMIT;
}
/* Allocate an object to hold data about this result set */
rdata = (ResultSetData*) ckalloc(sizeof(ResultSetData));
rdata->refCount = 1;
rdata->sdata = sdata;
rdata->stmtPtr = NULL;
rdata->paramValues = NULL;
rdata->paramBindings = NULL;
rdata->paramLengths = NULL;
rdata->rowCount = 0;
rdata->resultErrors = (my_bool*) ckalloc(nColumns * sizeof(my_bool));
rdata->resultNulls = (my_bool*) ckalloc(nColumns * sizeof(my_bool));
resultLengths = rdata->resultLengths = (unsigned long*)
ckalloc(nColumns * sizeof(unsigned long));
rdata->resultBindings = resultBindings = MysqlBindAlloc(nColumns);
IncrStatementRefCount(sdata);
Tcl_ObjectSetMetadata(thisObject, &resultSetDataType, (ClientData) rdata);
/* Make bindings for all the result columns. Defer binding variable
* length fields until first execution. */
if (nColumns > 0) {
fields = mysql_fetch_fields(sdata->metadataPtr);
}
for (i = 0; i < nColumns; ++i) {
MYSQL_FIELD* field = MysqlFieldIndex(fields, i);
switch (field->type) {
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
MysqlBindSetBufferType(resultBindings, i, MYSQL_TYPE_DOUBLE);
MysqlBindAllocBuffer(resultBindings, i, sizeof(double));
resultLengths[i] = sizeof(double);
break;
case MYSQL_TYPE_BIT:
MysqlBindSetBufferType(resultBindings, i, MYSQL_TYPE_BIT);
MysqlBindAllocBuffer(resultBindings, i, field->length);
resultLengths[i] = field->length;
break;
case MYSQL_TYPE_LONGLONG:
MysqlBindSetBufferType(resultBindings, i, MYSQL_TYPE_LONGLONG);
MysqlBindAllocBuffer(resultBindings, i, sizeof(Tcl_WideInt));
resultLengths[i] = sizeof(Tcl_WideInt);
break;
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
MysqlBindSetBufferType(resultBindings, i, MYSQL_TYPE_LONG);
MysqlBindAllocBuffer(resultBindings, i, sizeof(int));
resultLengths[i] = sizeof(int);
break;
default:
MysqlBindSetBufferType(resultBindings, i, MYSQL_TYPE_STRING);
MysqlBindAllocBuffer(resultBindings, i, 0);
resultLengths[i] = 0;
break;
}
MysqlBindSetLength(resultBindings, i, rdata->resultLengths + i);
rdata->resultNulls[i] = 0;
MysqlBindSetIsNull(resultBindings, i, rdata->resultNulls + i);
rdata->resultErrors[i] = 0;
MysqlBindSetError(resultBindings, i, rdata->resultErrors + i);
}
/*
* Find a statement handle that we can use to execute the SQL code.
* If the main statement handle associated with the statement
* is idle, we can use it. Otherwise, we have to allocate and
* prepare a fresh one.
*/
if (sdata->flags & STMT_FLAG_BUSY) {
rdata->stmtPtr = AllocAndPrepareStatement(interp, sdata);
if (rdata->stmtPtr == NULL) {
return TCL_ERROR;
}
} else {
rdata->stmtPtr = sdata->stmtPtr;
sdata->flags |= STMT_FLAG_BUSY;
}
/* Allocate the parameter bindings */
Tcl_ListObjLength(NULL, sdata->subVars, &nParams);
rdata->paramValues = Tcl_NewObj();
Tcl_IncrRefCount(rdata->paramValues);
rdata->paramBindings = MysqlBindAlloc(nParams);
rdata->paramLengths = (unsigned long*) ckalloc(nParams
* sizeof(unsigned long));
for (nBound = 0; nBound < nParams; ++nBound) {
MysqlBindSetBufferType(rdata->paramBindings, nBound, MYSQL_TYPE_NULL);
}
/* Bind the substituted parameters */
for (nBound = 0; nBound < nParams; ++nBound) {
Tcl_ListObjIndex(NULL, sdata->subVars, nBound, &paramNameObj);
paramName = Tcl_GetString(paramNameObj);
if (objc == skip+2) {
/* Param from a dictionary */
if (Tcl_DictObjGet(interp, objv[skip+1],
paramNameObj, &paramValObj) != TCL_OK) {
return TCL_ERROR;
}
} else {
/* Param from a variable */
paramValObj = Tcl_GetVar2Ex(interp, paramName, NULL,
TCL_LEAVE_ERR_MSG);
}
/*
* At this point, paramValObj contains the parameter to bind.
* Convert the parameters to the appropriate data types for
* MySQL's prepared statement interface, and bind them.
*/
if (paramValObj != NULL) {
switch (sdata->params[nBound].dataType & 0xffff) {
case MYSQL_TYPE_NEWDECIMAL:
case MYSQL_TYPE_DECIMAL:
if (sdata->params[nBound].scale == 0) {
if (sdata->params[nBound].precision < 10) {
goto smallinteger;
} else if (sdata->params[nBound].precision < 19) {
goto biginteger;
} else {
goto charstring;
}
} else if (sdata->params[nBound].precision < 17) {
goto real;
} else {
goto charstring;
}
case MYSQL_TYPE_FLOAT:
case MYSQL_TYPE_DOUBLE:
real:
MysqlBindSetBufferType(rdata->paramBindings, nBound,
MYSQL_TYPE_DOUBLE);
bufPtr = (char *)MysqlBindAllocBuffer(rdata->paramBindings,
nBound, sizeof(double));
rdata->paramLengths[nBound] = sizeof(double);
MysqlBindSetLength(rdata->paramBindings, nBound,
&(rdata->paramLengths[nBound]));
if (Tcl_GetDoubleFromObj(interp, paramValObj,
(double*) bufPtr) != TCL_OK) {
return TCL_ERROR;
}
break;
case MYSQL_TYPE_BIT:
case MYSQL_TYPE_LONGLONG:
biginteger:
MysqlBindSetBufferType(rdata->paramBindings, nBound,
MYSQL_TYPE_LONGLONG);
bufPtr = (char *)MysqlBindAllocBuffer(rdata->paramBindings, nBound,
sizeof(Tcl_WideInt));
rdata->paramLengths[nBound] = sizeof(Tcl_WideInt);
MysqlBindSetLength(rdata->paramBindings, nBound,
&(rdata->paramLengths[nBound]));
if (Tcl_GetWideIntFromObj(interp, paramValObj,
(Tcl_WideInt*) bufPtr) != TCL_OK) {
return TCL_ERROR;
}
break;
case MYSQL_TYPE_TINY:
case MYSQL_TYPE_SHORT:
case MYSQL_TYPE_INT24:
case MYSQL_TYPE_LONG:
smallinteger:
MysqlBindSetBufferType(rdata->paramBindings, nBound,
MYSQL_TYPE_LONG);
bufPtr = (char *)MysqlBindAllocBuffer(rdata->paramBindings, nBound,
sizeof(int));
rdata->paramLengths[nBound] = sizeof(int);
MysqlBindSetLength(rdata->paramBindings, nBound,
&(rdata->paramLengths[nBound]));
if (Tcl_GetIntFromObj(interp, paramValObj,
(int*) bufPtr) != TCL_OK) {
return TCL_ERROR;
}
break;
default:
charstring:
Tcl_ListObjAppendElement(NULL, rdata->paramValues, paramValObj);
if (sdata->params[nBound].dataType & IS_BINARY) {
MysqlBindSetBufferType(rdata->paramBindings, nBound,
MYSQL_TYPE_BLOB);
paramValStr = (char*)
Tcl_GetByteArrayFromObj(paramValObj, &len);
} else {
MysqlBindSetBufferType(rdata->paramBindings, nBound,
MYSQL_TYPE_STRING);
paramValStr = Tcl_GetStringFromObj(paramValObj, &len);
}
bufPtr = (char *)MysqlBindAllocBuffer(rdata->paramBindings, nBound,
len+1);
memcpy(bufPtr, paramValStr, len);
rdata->paramLengths[nBound] = len;
MysqlBindSetLength(rdata->paramBindings, nBound,
&(rdata->paramLengths[nBound]));
break;
}
} else {
MysqlBindSetBufferType(rdata->paramBindings, nBound,
MYSQL_TYPE_NULL);
}
}
/* Execute the statement */
/*
* It is tempting to conserve client memory here by omitting
* the call to 'mysql_stmt_store_result', but doing so causes
* 'calls out of sync' errors when attempting to prepare a
* statement while a result set is open. Certain of these errors
* can, in turn, be avoided by using mysql_stmt_set_attr
* and turning on "CURSOR_MODE_READONLY", but that, in turn
* causes the server summarily to disconnect the client in
* some tests.
*/
if (mysql_stmt_bind_param(rdata->stmtPtr, rdata->paramBindings)
|| ((nColumns > 0) && mysql_stmt_bind_result(rdata->stmtPtr,
resultBindings))
|| mysql_stmt_execute(rdata->stmtPtr)
|| mysql_stmt_store_result(rdata->stmtPtr) ) {
TransferMysqlStmtError(interp, sdata->stmtPtr);
return TCL_ERROR;
}
/* Determine and store the row count */
rdata->rowCount = mysql_stmt_affected_rows(sdata->stmtPtr);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* ResultSetColumnsMethod --
*
* Retrieves the list of columns from a result set.
*
* Usage:
* $resultSet columns
*
* Results:
* Returns the count of columns
*
*-----------------------------------------------------------------------------
*/
static int
ResultSetColumnsMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current result set object */
ResultSetData* rdata = (ResultSetData*)
Tcl_ObjectGetMetadata(thisObject, &resultSetDataType);
StatementData* sdata = (StatementData*) rdata->sdata;
(void)dummy;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "?pattern?");
return TCL_ERROR;
}
Tcl_SetObjResult(interp, sdata->columnNames);
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* ResultSetNextrowMethod --
*
* Retrieves the next row from a result set.
*
* Usage:
* $resultSet nextrow ?-as lists|dicts? ?--? variableName
*
* Options:
* -as Selects the desired form for returning the results.
*
* Parameters:
* variableName -- Variable in which the results are to be returned
*
* Results:
* Returns a standard Tcl result. The interpreter result is 1 if there
* are more rows remaining, and 0 if no more rows remain.
*
* Side effects:
* Stores in the given variable either a list or a dictionary
* containing one row of the result set.
*
*-----------------------------------------------------------------------------
*/
static int
ResultSetNextrowMethod(
ClientData clientData, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
int lists = PTR2INT(clientData);
/* Flag == 1 if lists are to be returned,
* 0 if dicts are to be returned */
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current result set object */
ResultSetData* rdata = (ResultSetData*)
Tcl_ObjectGetMetadata(thisObject, &resultSetDataType);
/* Data pertaining to the current result set */
StatementData* sdata = (StatementData*) rdata->sdata;
/* Statement that yielded the result set */
ConnectionData* cdata = (ConnectionData*) sdata->cdata;
/* Connection that opened the statement */
PerInterpData* pidata = (PerInterpData*) cdata->pidata;
/* Per interpreter data */
Tcl_Obj** literals = pidata->literals;
/* Literal pool */
int nColumns = 0; /* Number of columns in the result set */
Tcl_Obj* colName; /* Name of the current column */
Tcl_Obj* resultRow; /* Row of the result set under construction */
Tcl_Obj* colObj; /* Column obtained from the row */
int status = TCL_ERROR; /* Status return from this command */
MYSQL_FIELD* fields; /* Fields of the result set */
MYSQL_BIND* resultBindings = rdata->resultBindings;
/* Descriptions of the results */
unsigned long* resultLengths = rdata->resultLengths;
/* String lengths of the results */
my_bool* resultNulls = rdata->resultNulls;
/* Indicators that the results are null */
void* bufPtr; /* Pointer to a result's buffer */
unsigned char byte; /* One byte extracted from a bit field */
Tcl_WideInt bitVal; /* Value of a bit field */
int mysqlStatus; /* Status return from MySQL */
int i;
unsigned int j;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "varName");
return TCL_ERROR;
}
/* Get the column names in the result set. */
Tcl_ListObjLength(NULL, sdata->columnNames, &nColumns);
if (nColumns == 0) {
Tcl_SetObjResult(interp, literals[LIT_0]);
return TCL_OK;
}
resultRow = Tcl_NewObj();
Tcl_IncrRefCount(resultRow);
/*
* Try to rebind the result set before doing the next fetch
*/
fields = mysql_fetch_fields(sdata->metadataPtr);
if (mysql_stmt_bind_result(rdata->stmtPtr, resultBindings)) {
goto cleanup;
}
/* Fetch the row to determine sizes. */
mysqlStatus = mysql_stmt_fetch(rdata->stmtPtr);
if (mysqlStatus != 0 && mysqlStatus != MYSQL_DATA_TRUNCATED) {
if (mysqlStatus == MYSQL_NO_DATA) {
Tcl_SetObjResult(interp, literals[LIT_0]);
status = TCL_OK;
}
goto cleanup;
}
/* Retrieve one column at a time. */
for (i = 0; i < nColumns; ++i) {
MYSQL_FIELD* field = MysqlFieldIndex(fields, i);
colObj = NULL;
if (!resultNulls[i]) {
if (resultLengths[i]
> MysqlBindGetBufferLength(resultBindings, i)) {
MysqlBindFreeBuffer(resultBindings, i);
MysqlBindAllocBuffer(resultBindings, i, resultLengths[i] + 1);
if (mysql_stmt_fetch_column(rdata->stmtPtr,
MysqlBindIndex(resultBindings, i),
i, 0)) {
goto cleanup;
}
}
bufPtr = MysqlBindGetBuffer(resultBindings, i);
switch (MysqlBindGetBufferType(resultBindings, i)) {
case MYSQL_TYPE_BIT:
bitVal = 0;
for (j = 0; j < resultLengths[i]; ++j) {
byte = ((unsigned char*) bufPtr)[resultLengths[i]-1-j];
bitVal |= (byte << (8*j));
}
colObj = Tcl_NewWideIntObj(bitVal);
break;
case MYSQL_TYPE_DOUBLE:
colObj = Tcl_NewDoubleObj(*(double*) bufPtr);
break;
case MYSQL_TYPE_LONG:
colObj = Tcl_NewWideIntObj(*(int*) bufPtr);
break;
case MYSQL_TYPE_LONGLONG:
colObj = Tcl_NewWideIntObj(*(Tcl_WideInt*) bufPtr);
break;
default:
if (field->charsetnr == 63) {
colObj = Tcl_NewByteArrayObj((unsigned char*) bufPtr,
resultLengths[i]);
} else {
colObj = Tcl_NewStringObj((char*) bufPtr,
resultLengths[i]);
}
break;
}
}
if (lists) {
if (colObj == NULL) {
colObj = literals[LIT_EMPTY];
}
Tcl_ListObjAppendElement(NULL, resultRow, colObj);
} else {
if (colObj != NULL) {
Tcl_ListObjIndex(NULL, sdata->columnNames, i, &colName);
Tcl_DictObjPut(NULL, resultRow, colName, colObj);
}
}
}
/* Save the row in the given variable */
if (Tcl_SetVar2Ex(interp, Tcl_GetString(objv[2]), NULL,
resultRow, TCL_LEAVE_ERR_MSG) == NULL) {
goto cleanup;
}
Tcl_SetObjResult(interp, literals[LIT_1]);
status = TCL_OK;
cleanup:
if (status != TCL_OK) {
TransferMysqlStmtError(interp, rdata->stmtPtr);
}
Tcl_DecrRefCount(resultRow);
return status;
}
/*
*-----------------------------------------------------------------------------
*
* ResultSetRowcountMethod --
*
* Returns (if known) the number of rows affected by a MySQL statement.
*
* Usage:
* $resultSet rowcount
*
* Results:
* Returns a standard Tcl result giving the number of affected rows.
*
*-----------------------------------------------------------------------------
*/
static int
ResultSetRowcountMethod(
ClientData dummy, /* Not used */
Tcl_Interp* interp, /* Tcl interpreter */
Tcl_ObjectContext context, /* Object context */
int objc, /* Parameter count */
Tcl_Obj *const objv[] /* Parameter vector */
) {
Tcl_Object thisObject = Tcl_ObjectContextObject(context);
/* The current result set object */
ResultSetData* rdata = (ResultSetData*)
Tcl_ObjectGetMetadata(thisObject, &resultSetDataType);
/* Data pertaining to the current result set */
(void)dummy;
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, "");
return TCL_ERROR;
}
Tcl_SetObjResult(interp,
Tcl_NewWideIntObj((Tcl_WideInt)(rdata->rowCount)));
return TCL_OK;
}
/*
*-----------------------------------------------------------------------------
*
* DeleteResultSetMetadata, DeleteResultSet --
*
* Cleans up when a MySQL result set is no longer required.
*
* Side effects:
* Frees all resources associated with the result set.
*
*-----------------------------------------------------------------------------
*/
static void
DeleteResultSetMetadata(
ClientData clientData /* Instance data for the connection */
) {
DecrResultSetRefCount((ResultSetData*)clientData);
}
static void
DeleteResultSet(
ResultSetData* rdata /* Metadata for the result set */
) {
StatementData* sdata = rdata->sdata;
int i;
int nParams;
int nColumns;
Tcl_ListObjLength(NULL, sdata->subVars, &nParams);
Tcl_ListObjLength(NULL, sdata->columnNames, &nColumns);
for (i = 0; i < nColumns; ++i) {
MysqlBindFreeBuffer(rdata->resultBindings, i);
}
ckfree((char*)(rdata->resultBindings));
ckfree((char*)(rdata->resultLengths));
ckfree((char*)(rdata->resultNulls));
ckfree((char*)(rdata->resultErrors));
ckfree((char*)(rdata->paramLengths));
if (rdata->paramBindings != NULL) {
for (i = 0; i < nParams; ++i) {
if (MysqlBindGetBufferType(rdata->paramBindings, i)
!= MYSQL_TYPE_NULL) {
MysqlBindFreeBuffer(rdata->paramBindings, i);
}
}
ckfree((char*)(rdata->paramBindings));
}
if (rdata->paramValues != NULL) {
Tcl_DecrRefCount(rdata->paramValues);
}
if (rdata->stmtPtr != NULL) {
if (rdata->stmtPtr != sdata->stmtPtr) {
mysql_stmt_close(rdata->stmtPtr);
} else {
sdata->flags &= ~ STMT_FLAG_BUSY;
}
}
DecrStatementRefCount(rdata->sdata);
ckfree((char*)rdata);
}
/*
*-----------------------------------------------------------------------------
*
* CloneResultSet --
*
* Attempts to clone a MySQL result set's metadata.
*
* Results:
* Returns the new metadata
*
* At present, we don't attempt to clone result sets - it's not obvious
* that such an action would ever even make sense. Instead, we throw an
* error.
*
*-----------------------------------------------------------------------------
*/
static int
CloneResultSet(
Tcl_Interp* interp, /* Tcl interpreter for error reporting */
ClientData metadata, /* Metadata to be cloned */
ClientData* newMetaData /* Where to put the cloned metadata */
) {
(void)metadata;
(void)newMetaData;
Tcl_SetObjResult(interp,
Tcl_NewStringObj("MySQL result sets are not clonable",
-1));
return TCL_ERROR;
}
/*
*-----------------------------------------------------------------------------
*
* Tdbcmysql_Init --
*
* Initializes the TDBC-MYSQL bridge when this library is loaded.
*
* Side effects:
* Creates the ::tdbc::mysql namespace and the commands that reside in it.
* Initializes the MYSQL environment.
*
*-----------------------------------------------------------------------------
*/
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
DLLEXPORT int
Tdbcmysql_Init(
Tcl_Interp* interp /* Tcl interpreter */
) {
PerInterpData* pidata; /* Per-interpreter data for this package */
Tcl_Obj* nameObj; /* Name of a class or method being looked up */
Tcl_Object curClassObject; /* Tcl_Object representing the current class */
Tcl_Class curClass; /* Tcl_Class representing the current class */
int i;
/* Require all package dependencies */
if (Tcl_InitStubs(interp, TCL_VERSION, 0) == NULL) {
return TCL_ERROR;
}
if (TclOOInitializeStubs(interp, "1.0") == NULL) {
return TCL_ERROR;
}
if (Tdbc_InitStubs(interp) == NULL) {
return TCL_ERROR;
}
/* Provide the current package */
if (Tcl_PkgProvideEx(interp, "tdbc::mysql", PACKAGE_VERSION, NULL) == TCL_ERROR) {
return TCL_ERROR;
}
/*
* Create per-interpreter data for the package
*/
pidata = (PerInterpData*) ckalloc(sizeof(PerInterpData));
pidata->refCount = 1;
for (i = 0; i < LIT__END; ++i) {
pidata->literals[i] = Tcl_NewStringObj(LiteralValues[i], -1);
Tcl_IncrRefCount(pidata->literals[i]);
}
Tcl_InitHashTable(&(pidata->typeNumHash), TCL_ONE_WORD_KEYS);
for (i = 0; dataTypes[i].name != NULL; ++i) {
int isNew;
Tcl_HashEntry* entry =
Tcl_CreateHashEntry(&(pidata->typeNumHash),
INT2PTR(dataTypes[i].num),
&isNew);
Tcl_Obj* nameObj = Tcl_NewStringObj(dataTypes[i].name, -1);
Tcl_IncrRefCount(nameObj);
Tcl_SetHashValue(entry, (ClientData) nameObj);
}
/*
* Find the connection class, and attach an 'init' method to it.
*/
nameObj = Tcl_NewStringObj("::tdbc::mysql::connection", -1);
Tcl_IncrRefCount(nameObj);
if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
Tcl_DecrRefCount(nameObj);
return TCL_ERROR;
}
Tcl_DecrRefCount(nameObj);
curClass = Tcl_GetObjectAsClass(curClassObject);
/* Attach the constructor to the 'connection' class */
Tcl_ClassSetConstructor(interp, curClass,
Tcl_NewMethod(interp, curClass, NULL, 1,
&ConnectionConstructorType,
(ClientData) pidata));
/* Attach the methods to the 'connection' class */
for (i = 0; ConnectionMethods[i] != NULL; ++i) {
nameObj = Tcl_NewStringObj(ConnectionMethods[i]->name, -1);
Tcl_IncrRefCount(nameObj);
Tcl_NewMethod(interp, curClass, nameObj, 1, ConnectionMethods[i],
(ClientData) NULL);
Tcl_DecrRefCount(nameObj);
}
/* Look up the 'statement' class */
nameObj = Tcl_NewStringObj("::tdbc::mysql::statement", -1);
Tcl_IncrRefCount(nameObj);
if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
Tcl_DecrRefCount(nameObj);
return TCL_ERROR;
}
Tcl_DecrRefCount(nameObj);
curClass = Tcl_GetObjectAsClass(curClassObject);
/* Attach the constructor to the 'statement' class */
Tcl_ClassSetConstructor(interp, curClass,
Tcl_NewMethod(interp, curClass, NULL, 1,
&StatementConstructorType,
(ClientData) NULL));
/* Attach the methods to the 'statement' class */
for (i = 0; StatementMethods[i] != NULL; ++i) {
nameObj = Tcl_NewStringObj(StatementMethods[i]->name, -1);
Tcl_IncrRefCount(nameObj);
Tcl_NewMethod(interp, curClass, nameObj, 1, StatementMethods[i],
(ClientData) NULL);
Tcl_DecrRefCount(nameObj);
}
/* Look up the 'resultSet' class */
nameObj = Tcl_NewStringObj("::tdbc::mysql::resultset", -1);
Tcl_IncrRefCount(nameObj);
if ((curClassObject = Tcl_GetObjectFromObj(interp, nameObj)) == NULL) {
Tcl_DecrRefCount(nameObj);
return TCL_ERROR;
}
Tcl_DecrRefCount(nameObj);
curClass = Tcl_GetObjectAsClass(curClassObject);
/* Attach the constructor to the 'resultSet' class */
Tcl_ClassSetConstructor(interp, curClass,
Tcl_NewMethod(interp, curClass, NULL, 1,
&ResultSetConstructorType,
(ClientData) NULL));
/* Attach the methods to the 'resultSet' class */
for (i = 0; ResultSetMethods[i] != NULL; ++i) {
nameObj = Tcl_NewStringObj(ResultSetMethods[i]->name, -1);
Tcl_IncrRefCount(nameObj);
Tcl_NewMethod(interp, curClass, nameObj, 1, ResultSetMethods[i],
(ClientData) NULL);
Tcl_DecrRefCount(nameObj);
}
nameObj = Tcl_NewStringObj("nextlist", -1);
Tcl_IncrRefCount(nameObj);
Tcl_NewMethod(interp, curClass, nameObj, 1, &ResultSetNextrowMethodType,
(ClientData) 1);
Tcl_DecrRefCount(nameObj);
nameObj = Tcl_NewStringObj("nextdict", -1);
Tcl_IncrRefCount(nameObj);
Tcl_NewMethod(interp, curClass, nameObj, 1, &ResultSetNextrowMethodType,
(ClientData) 0);
Tcl_DecrRefCount(nameObj);
/*
* Initialize the MySQL library if this is the first interp using it
*/
Tcl_MutexLock(&mysqlMutex);
if (mysqlRefCount == 0) {
if ((mysqlLoadHandle = MysqlInitStubs(interp)) == NULL) {
Tcl_MutexUnlock(&mysqlMutex);
return TCL_ERROR;
}
mysql_library_init(0, NULL, NULL);
mysqlClientVersion = mysql_get_client_version();
}
++mysqlRefCount;
Tcl_MutexUnlock(&mysqlMutex);
/*
* TODO: mysql_thread_init, and keep a TSD reference count of users.
*/
return TCL_OK;
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
/*
*-----------------------------------------------------------------------------
*
* DeletePerInterpData --
*
* Delete per-interpreter data when the MYSQL package is finalized
*
* Side effects:
* Releases the (presumably last) reference on the environment handle,
* cleans up the literal pool, and deletes the per-interp data structure.
*
*-----------------------------------------------------------------------------
*/
static void
DeletePerInterpData(
PerInterpData* pidata /* Data structure to clean up */
) {
int i;
Tcl_HashSearch search;
Tcl_HashEntry *entry;
for (entry = Tcl_FirstHashEntry(&(pidata->typeNumHash), &search);
entry != NULL;
entry = Tcl_NextHashEntry(&search)) {
Tcl_Obj* nameObj = (Tcl_Obj*) Tcl_GetHashValue(entry);
Tcl_DecrRefCount(nameObj);
}
Tcl_DeleteHashTable(&(pidata->typeNumHash));
for (i = 0; i < LIT__END; ++i) {
Tcl_DecrRefCount(pidata->literals[i]);
}
ckfree((char *) pidata);
/*
* TODO: decrease thread refcount and mysql_thread_end if need be
*/
Tcl_MutexLock(&mysqlMutex);
if (--mysqlRefCount == 0) {
mysql_library_end();
Tcl_FSUnloadFile(NULL, mysqlLoadHandle);
}
Tcl_MutexUnlock(&mysqlMutex);
}