mariadb/innobase/odbc/odbc0odbc.c
2001-02-17 14:19:19 +02:00

714 lines
16 KiB
C

/******************************************************
Innobase ODBC client library
(c) 1998 Innobase Oy
Created 2/22/1998 Heikki Tuuri
*******************************************************/
#include "odbc0odbc.h"
#include "mem0mem.h"
#include "com0com.h"
#include "usr0sess.h"
#define ODBC_STAT_INITIAL 1
#define ODBC_STAT_PREPARED 2
#define ODBC_STAT_EXECUTED 3
typedef struct odbc_conn_struct odbc_conn_t;
typedef struct odbc_env_struct odbc_env_t;
/* ODBC parameter description struct */
typedef struct odbc_param_struct odbc_param_t;
struct odbc_param_struct{
ulint data_type; /* SQL_CHAR, ... */
ibool is_input; /* TRUE if an input parameter of a stored
procedure, FALSE if an output parameter */
byte* buf; /* buffer where the value is stored before
SQLExecute, or where it comes after SQLExecute
in the case of an output parameter */
lint* data_len; /* pointer to where the data len or the value
SQL_NULL_DATA is stored */
ulint buf_len; /* buffer size */
};
/* ODBC statement data structure */
typedef struct odbc_stat_struct odbc_stat_t;
struct odbc_stat_struct{
ulint state; /* ODBC_STAT_INITIAL,
ODBC_STAT_PREPARED,
ODBC_STAT_EXECUTED */
ulint id; /* statement id */
ulint n_params; /* number of parameters */
odbc_param_t* params; /* pointer to an array describing
the parameters, if any */
ulint n_params_bound; /* number of parameters that have
been bound: the statement cannot be
executed before n_params_bound
== n_params */
byte* error_msg; /* possible error message, or NULL;
allocated separately from dynamic
memory */
ulint error_msg_len; /* error mesage length if it is
non-NULL */
odbc_conn_t* conn; /* connection */
UT_LIST_NODE_T(odbc_stat_t)
stat_list; /* list of the statements of the
connection */
};
/* ODBC connection data structure */
struct odbc_conn_struct{
ibool connected; /* TRUE if connected */
char* server_name; /* server name where connected
(= server address) */
ulint server_name_len;/* length of the server name */
com_endpoint_t* com_endpoint; /* connection endpoint for this client
connection */
dulint out_msg_count; /* count of outgoing messages */
byte* out_datagram_buf;/* buffer for outgoing datagrams to
the server */
byte* in_datagram_buf;/* buffer for incoming datagrams from
the server */
byte* addr_buf; /* buffer for the address from which
an incoming datagram came; in practice,
this will be the server address */
dulint sess_id; /* user session id, once the
connection to the server is
established */
odbc_env_t* env; /* environment */
UT_LIST_BASE_NODE_T(odbc_stat_t)
stat_list; /* list of the statements of the
connection */
UT_LIST_NODE_T(odbc_conn_t)
conn_list; /* list of the connections of the
environment */
};
/* ODBC environment data structure */
struct odbc_env_struct{
UT_LIST_BASE_NODE_T(odbc_conn_t) conn_list;
/* list of the connections of the
environment */
};
/**************************************************************************
Gets the nth parameter description struct for a statement. */
UNIV_INLINE
odbc_param_t*
stat_get_nth_param(
/*===============*/
/* out: nth parameter */
odbc_stat_t* stat, /* in: pointer to statement handle */
ulint i) /* in: parameter index */
{
ut_ad(stat->n_params > i);
return(stat->params + i);
}
/**************************************************************************
Allocates an SQL environment. */
RETCODE
SQLAllocEnv(
/*========*/
/* out: SQL_SUCCESS */
HENV* phenv) /* out: pointer to an environment handle */
{
odbc_env_t* env;
if (!sync_initialized) {
sync_init();
mem_init(2000000);
}
env = mem_alloc(sizeof(odbc_env_t));
UT_LIST_INIT(env->conn_list);
*phenv = env;
return(SQL_SUCCESS);
}
/**************************************************************************
Allocates an SQL connection. */
RETCODE
SQLAllocConnect(
/*============*/
/* out: SQL_SUCCESS */
HENV henv, /* in: pointer to an environment handle */
HDBC* phdbc) /* out: pointer to a connection handle */
{
odbc_conn_t* conn;
odbc_env_t* env;
ut_a(henv);
env = henv;
conn = mem_alloc(sizeof(odbc_conn_t));
conn->connected = FALSE;
conn->env = env;
UT_LIST_INIT(conn->stat_list);
UT_LIST_ADD_LAST(conn_list, env->conn_list, conn);
*phdbc = conn;
return(SQL_SUCCESS);
}
/**************************************************************************
Allocates an SQL statement. */
RETCODE
SQLAllocStmt(
/*=========*/
HDBC hdbc, /* in: SQL connection */
HSTMT* phstmt) /* out: pointer to a statement handle */
{
odbc_conn_t* conn;
odbc_stat_t* stat;
ut_a(hdbc);
conn = hdbc;
stat = mem_alloc(sizeof(odbc_stat_t));
stat->state = ODBC_STAT_INITIAL;
stat->error_msg = NULL;
stat->conn = conn;
UT_LIST_ADD_LAST(stat_list, conn->stat_list, stat);
*phstmt = stat;
return(SQL_SUCCESS);
}
/**************************************************************************
Sends the message in datagram_buf to the server. */
static
void
odbc_send_cli_msg(
/*==============*/
odbc_conn_t* conn, /* in: connection, does not have to be
connected yet */
ulint len) /* in: message length (excluding the standard
header of size SESS_CLI_MSG_DATA) */
{
ulint ret;
ulint fold;
byte* msg;
ut_a(len + SESS_CLI_MSG_DATA <= ODBC_DATAGRAM_SIZE);
msg = conn->out_datagram_buf;
mach_write_to_8(msg + SESS_CLI_MSG_NO, conn->out_msg_count);
UT_DULINT_INC(conn->out_msg_count);
fold = ut_fold_binary(msg + 4, len + SESS_CLI_MSG_DATA - 4);
ut_ad(SESS_CLI_MSG_CHECKSUM == 0);
mach_write_to_4(msg + SESS_CLI_MSG_CHECKSUM, fold);
ret = com_sendto(conn->com_endpoint, msg, SESS_CLI_MSG_DATA + len,
conn->server_name, conn->server_name_len);
ut_a(ret == 0);
}
/**************************************************************************
Receives a message in datagram_buf from the server. */
static
void
odbc_recv_srv_msg(
/*==============*/
odbc_conn_t* conn, /* in: connection, does not have to be
connected yet */
ulint* len) /* out: received message length (excluding the
standard header of size SESS_SRV_MSG_DATA) */
{
ulint total_len;
ulint addr_len;
ulint ret;
ret = com_recvfrom(conn->com_endpoint, conn->in_datagram_buf,
ODBC_DATAGRAM_SIZE, &total_len, (char*)conn->addr_buf,
ODBC_ADDRESS_SIZE, &addr_len);
ut_a(ret == 0);
ut_a(total_len >= SESS_SRV_MSG_DATA);
*len = total_len - SESS_SRV_MSG_DATA;
}
/**************************************************************************
Connects to a database server process (establishes a connection and a
session). */
RETCODE
SQLConnect(
/*=======*/
/* out: SQL_SUCCESS */
HDBC hdbc, /* in: SQL connection handle */
UCHAR* szDSN, /* in: data source name (server name) */
SWORD cbDSN, /* in: data source name length */
UCHAR* szUID, /* in: user name */
SWORD cbUID, /* in: user name length */
UCHAR* szAuthStr, /* in: password */
SWORD cbAuthStr) /* in: password length */
{
com_endpoint_t* ep;
odbc_conn_t* conn;
ulint err;
ulint size;
byte* msg;
ulint len;
UCHAR catenated_name[100];
ut_a(hdbc && szDSN);
UT_NOT_USED(szUID);
UT_NOT_USED(cbUID);
UT_NOT_USED(szAuthStr);
UT_NOT_USED(cbAuthStr);
conn = hdbc;
ut_a(!conn->connected);
conn->server_name = mem_alloc(cbDSN);
ut_memcpy(conn->server_name, szDSN, cbDSN);
conn->server_name_len = cbDSN;
ep = com_endpoint_create(COM_SHM);
ut_a(ep);
conn->com_endpoint = ep;
conn->out_msg_count = ut_dulint_zero;
size = ODBC_DATAGRAM_SIZE;
err = com_endpoint_set_option(ep, COM_OPT_MAX_DGRAM_SIZE,
(byte*)&size, 4);
ut_a(err == 0);
/* Make the data source name catenated to user name as the
address of the communications endpoint */
ut_a((ulint)cbDSN + (ulint)cbUID < 100);
ut_memcpy(catenated_name, szDSN, (ulint)cbDSN);
ut_memcpy(catenated_name + (ulint)cbDSN, szUID, (ulint)cbUID);
err = com_bind(ep, (char*)catenated_name, (ulint)cbDSN
+ (ulint)cbUID);
ut_a(err == 0);
conn->in_datagram_buf = mem_alloc(ODBC_DATAGRAM_SIZE);
msg = mem_alloc(ODBC_DATAGRAM_SIZE);
conn->out_datagram_buf = msg;
conn->addr_buf = mem_alloc(ODBC_ADDRESS_SIZE);
/* Set the session id to dulint 0 as we are not yet connected */
sess_cli_msg_set_sess(msg, ut_dulint_zero);
sess_cli_msg_set_type(msg, SESS_CLI_CONNECT);
/*------------------------------------------*/
odbc_send_cli_msg(conn, 0);
odbc_recv_srv_msg(conn, &len);
/*------------------------------------------*/
ut_a(len == 0);
ut_a(sess_srv_msg_get_type(conn->in_datagram_buf)
== SESS_SRV_ACCEPT_CONNECT);
conn->sess_id = mach_read_from_8(conn->in_datagram_buf
+ SESS_SRV_MSG_SESS_ID);
/* Write the session id to out_datagram_buf: it will not be rewritten
until the connection is closed, as the session id will stay the same */
sess_cli_msg_set_sess(msg, conn->sess_id);
/* We currently only send single part messages: the following will
stay 0 during the connection */
mach_write_to_4(msg + SESS_CLI_MSG_CONTINUE, 0);
mach_write_to_4(msg + SESS_CLI_MSG_CONT_SIZE, 0);
conn->connected = TRUE;
return(SQL_SUCCESS);
}
/**************************************************************************
Stores an error message to a statement handle, so that it can be later
queried with SQLError. */
static
void
odbc_stat_store_error_msg(
/*======================*/
odbc_stat_t* stat, /* in: statement handle */
byte* msg, /* in: error message sent by the server */
ulint len) /* in: length of msg */
{
if (stat->error_msg) {
mem_free(stat->error_msg);
}
stat->error_msg_len = len;
len += SESS_SRV_MSG_DATA;
stat->error_msg = mem_alloc(len);
ut_memcpy(stat->error_msg, msg, len);
}
/**************************************************************************
Queries an error message. */
RETCODE
SQLError(
/*=====*/
/* out: SQL_SUCCESS or SQL_NO_DATA_FOUND */
HENV henv, /* in: SQL_NULL_HENV */
HDBC hdbc, /* in: SQL_NULL_HDBC */
HSTMT hstmt, /* in: statement handle */
UCHAR* szSqlState, /* in/out: SQLSTATE as a null-terminated string,
(currently, always == "S1000") */
SDWORD* pfNativeError, /* out: native error code */
UCHAR* szErrorMsg, /* in/out: buffer for an error message as a
null-terminated string */
SWORD cbErrorMsgMax, /* in: buffer size for szErrorMsg */
SWORD* pcbErrorMsg) /* out: error message length */
{
odbc_stat_t* stat;
ulint len;
ut_a(henv == SQL_NULL_HENV);
ut_a(hdbc == SQL_NULL_HDBC);
ut_a(hstmt);
ut_a(cbErrorMsgMax > 1);
stat = hstmt;
if (stat->error_msg == NULL) {
return(SQL_NO_DATA_FOUND);
}
*pfNativeError = 0;
ut_memcpy(szSqlState, "S1000", 6);
len = (ulint)cbErrorMsgMax - 1;
if (stat->error_msg_len < len) {
len = stat->error_msg_len;
}
ut_memcpy(szErrorMsg, stat->error_msg + SESS_SRV_MSG_DATA, len);
*(szErrorMsg + len) = '\0';
*pcbErrorMsg = (SWORD)len;
if (stat->error_msg) {
mem_free(stat->error_msg);
stat->error_msg = NULL;
}
return(SQL_SUCCESS);
}
/**************************************************************************
Makes the server to parse and optimize an SQL string. */
RETCODE
SQLPrepare(
/*=======*/
/* out: SQL_SUCCESS or SQL_ERROR */
HSTMT hstmt, /* in: statement handle */
UCHAR* szSqlStr, /* in: SQL string */
SDWORD cbSqlStr) /* in: SQL string length */
{
odbc_stat_t* stat;
odbc_conn_t* conn;
odbc_param_t* param;
ulint len;
byte* msg;
ulint i;
stat = hstmt;
conn = stat->conn;
if (stat->error_msg) {
mem_free(stat->error_msg);
stat->error_msg = NULL;
}
ut_memcpy(conn->out_datagram_buf + SESS_CLI_MSG_DATA, szSqlStr,
1 + (ulint)cbSqlStr);
sess_cli_msg_set_type(conn->out_datagram_buf, SESS_CLI_PREPARE);
/* The client message will be decoded in sess_receive_prepare */
/*------------------------------------------*/
odbc_send_cli_msg(conn, 1 + (ulint)cbSqlStr);
odbc_recv_srv_msg(conn, &len);
/*------------------------------------------*/
/* The server message was coded in sess_receive_prepare */
ut_a(len >= 8);
msg = conn->in_datagram_buf;
if (sess_srv_msg_get_type(msg) != SESS_SRV_SUCCESS) {
ut_a(sess_srv_msg_get_type(msg) == SESS_SRV_ERROR);
odbc_stat_store_error_msg(stat, msg, len);
return(SQL_ERROR);
}
stat->id = mach_read_from_4(msg + SESS_SRV_MSG_DATA);
stat->n_params = mach_read_from_4(msg + SESS_SRV_MSG_DATA + 4);
stat->n_params_bound = 0;
ut_a(len == 8 + stat->n_params);
if (stat->n_params > 0) {
stat->params = mem_alloc(stat->n_params
* sizeof(odbc_param_t));
for (i = 0; i < stat->n_params; i++) {
param = stat_get_nth_param(stat, i);
param->is_input = mach_read_from_1(
msg + SESS_SRV_MSG_DATA + 8 + i);
/* Set buf to NULL so that we know when the parameter
has been bound */
param->buf = NULL;
}
}
stat->state = ODBC_STAT_PREPARED;
return(SQL_SUCCESS);
}
/**************************************************************************
Binds a parameter in a prepared statement. */
RETCODE
SQLBindParameter(
/*=============*/
/* out: SQL_SUCCESS */
HSTMT hstmt, /* in: statement handle */
UWORD ipar, /* in: parameter index, starting from 1 */
SWORD fParamType, /* in: SQL_PARAM_INPUT or SQL_PARAM_OUTPUT */
SWORD fCType, /* in: SQL_C_CHAR, ... */
SWORD fSqlType, /* in: SQL_CHAR, ... */
UDWORD cbColDef, /* in: precision: ignored */
SWORD ibScale, /* in: scale: ignored */
PTR rgbValue, /* in: pointer to a buffer for the data */
SDWORD cbValueMax, /* in: buffer size */
SDWORD* pcbValue) /* in: pointer to a buffer for the data
length or SQL_NULL_DATA */
{
odbc_stat_t* stat;
odbc_param_t* param;
stat = hstmt;
ut_a(stat->state != ODBC_STAT_INITIAL);
ut_a(rgbValue);
ut_a(ipar <= stat->n_params);
ut_a(ipar > 0);
ut_a(cbValueMax >= 0);
ut_a(pcbValue);
UT_NOT_USED(ibScale);
UT_NOT_USED(fCType);
UT_NOT_USED(cbColDef);
if (stat->error_msg) {
mem_free(stat->error_msg);
stat->error_msg = NULL;
}
param = stat_get_nth_param(stat, ipar - 1);
if (param->buf == NULL) {
stat->n_params_bound++;
}
param->data_type = fSqlType;
ut_a((fParamType != SQL_PARAM_INPUT) || param->is_input);
ut_a((fParamType == SQL_PARAM_INPUT) || !param->is_input);
param->buf = rgbValue;
param->buf_len = cbValueMax;
param->data_len = pcbValue;
return(SQL_SUCCESS);
}
/**************************************************************************
Executes a prepared statement where all parameters have been bound. */
RETCODE
SQLExecute(
/*=======*/
/* out: SQL_SUCCESS or SQL_ERROR */
HSTMT hstmt) /* in: statement handle */
{
odbc_stat_t* stat;
odbc_conn_t* conn;
odbc_param_t* param;
lint len;
ulint msg_len;
byte* msg;
byte* ptr;
lint int_val;
ulint i;
stat = hstmt;
ut_a(stat->state != ODBC_STAT_INITIAL);
ut_a(stat->n_params == stat->n_params_bound);
if (stat->error_msg) {
mem_free(stat->error_msg);
stat->error_msg = NULL;
}
conn = stat->conn;
msg = conn->out_datagram_buf;
sess_cli_msg_set_type(msg, SESS_CLI_EXECUTE);
ptr = msg + SESS_CLI_MSG_DATA;
mach_write_to_4(ptr, stat->id);
ptr += 4;
for (i = 0; i < stat->n_params; i++) {
param = stat_get_nth_param(stat, i);
if (param->is_input) {
/* Copy its length and data to the message buffer */
len = *(param->data_len);
mach_write_to_4(ptr, (ulint)len);
ptr += 4;
if (len != SQL_NULL_DATA) {
if (param->data_type == SQL_INTEGER) {
ut_ad(len == 4);
int_val = *((lint*)(param->buf));
mach_write_to_4(ptr, (ulint)int_val);
} else {
ut_memcpy(ptr, param->buf, len);
}
ptr += len;
}
}
}
/* The client message will be decoded in sess_receive_command */
/*------------------------------------------*/
odbc_send_cli_msg(conn, ptr - (msg + SESS_CLI_MSG_DATA));
odbc_recv_srv_msg(conn, &msg_len);
/*------------------------------------------*/
/* The server message was coded in sess_command_completed_message */
msg = conn->in_datagram_buf;
if (sess_srv_msg_get_type(msg) != SESS_SRV_SUCCESS) {
ut_a(sess_srv_msg_get_type(msg) == SESS_SRV_ERROR);
odbc_stat_store_error_msg(stat, msg, msg_len);
return(SQL_ERROR);
}
ptr = msg + SESS_SRV_MSG_DATA;
for (i = 0; i < stat->n_params; i++) {
param = stat_get_nth_param(stat, i);
if (!param->is_input) {
/* Copy its length and data from the message buffer */
len = (lint)mach_read_from_4(ptr);
ptr += 4;
*(param->data_len) = len;
if (len != SQL_NULL_DATA) {
if (param->data_type == SQL_INTEGER) {
ut_ad(len == 4);
int_val = (lint)mach_read_from_4(ptr);
*((lint*)(param->buf)) = int_val;
} else {
ut_memcpy(param->buf, ptr, (ulint)len);
}
ptr += len;
}
}
}
ut_ad(msg + SESS_SRV_MSG_DATA + msg_len == ptr);
return(SQL_SUCCESS);
}