/* This file is part of vmod-sql
Copyright (C) 2013 Sergey Poznyakoff
Vmod-sql is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
Vmod-sql is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with vmod-sql. If not, see .
*/
#include "vmod-sql.h"
#include
#include
#include
struct vmod_mysql_data
{
MYSQL *mysql;
MYSQL_RES *result;
char *buffer;
size_t bufsize;
};
static void
check_errno(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
switch (mysql_errno(mp->mysql)) {
case CR_SERVER_GONE_ERROR:
case CR_SERVER_LOST:
case ER_SERVER_SHUTDOWN:
case ER_ABORTING_CONNECTION:
i_modsql_error("query failed: %s", mysql_error(mp->mysql));
i_modsql_disconnect(conn);
if (conn->state == state_error) {
conn->state = state_disabled;
i_modsql_error("disabling MySQL connection");
}
break;
case ER_UNKNOWN_COM_ERROR:
case ER_ACCESS_DENIED_ERROR:
case ER_BAD_DB_ERROR:
case ER_WRONG_DB_NAME:
case ER_BAD_FIELD_ERROR:
case ER_BAD_HOST_ERROR:
case ER_BAD_TABLE_ERROR:
case ER_WRONG_FIELD_SPEC:
case ER_PARSE_ERROR:
case ER_EMPTY_QUERY:
case ER_FIELD_SPECIFIED_TWICE:
case ER_NO_SUCH_TABLE:
case ER_NOT_ALLOWED_COMMAND:
i_modsql_error("query failed: %s", mysql_error(mp->mysql));
}
}
/* ************************************************************************* */
/* Interface routines */
static int
s_mysql_init(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = calloc(1, sizeof (*mp));
if (!mp) {
i_modsql_error("not enough memory");
return -1;
}
conn->data = mp;
return 0;
}
static void
s_mysql_destroy(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
free(mp->buffer);
free(mp->mysql);
free(mp);
conn->data = NULL;
}
static int
s_mysql_connect(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
char *host, *socket_name = NULL;
char *s;
int port = 0;
mp->mysql = malloc(sizeof(MYSQL));
if (!mp->mysql) {
i_modsql_error("not enough memory");
conn->state = state_disabled;
return -1;
}
mysql_init(mp->mysql);
host = i_modsql_findparam(conn->param, "server");
if (host && host[0] == '/') {
host = "localhost";
socket_name = host;
}
s = i_modsql_findparam(conn->param, "port");
if (s)
port = atoi(s);
s = i_modsql_findparam(conn->param, "config");
if (s)
mysql_options(mp->mysql, MYSQL_READ_DEFAULT_FILE, s);
s = i_modsql_findparam(conn->param, "group");
if (s)
mysql_options(mp->mysql, MYSQL_READ_DEFAULT_GROUP, s);
s = i_modsql_findparam(conn->param, "cacert");
if (s)
mysql_ssl_set(mp->mysql, NULL, NULL, s, NULL, NULL);
debug(conn, 1, ("connecting to database"));
if (!mysql_real_connect(mp->mysql,
host,
i_modsql_findparam(conn->param, "user"),
i_modsql_findparam(conn->param, "password"),
i_modsql_findparam(conn->param, "database"),
port,
socket_name,
CLIENT_MULTI_RESULTS)) {
i_modsql_error("cannot connect: %s", mysql_error(mp->mysql));
return -1;
}
debug(conn, 1, ("connected to database"));
return 0;
}
static int
s_mysql_disconnect(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
mysql_close(mp->mysql);
return 0;
}
static int
s_mysql_query(struct vmod_sql_connection *conn, const char *query)
{
struct vmod_mysql_data *mp = conn->data;
int rc;
int i;
MYSQL *mysql;
for (i = 0; i < 10; i++) {
mysql = mp->mysql;
rc = mysql_query(mysql, query);
if (rc) {
check_errno(conn);
if (conn->state != state_init)
return -1;
i_modsql_connect(conn);
if (conn->state != state_connected)
return -1;
continue;
}
mp->result = mysql_store_result(mp->mysql);
if (mp->result) {
conn->state = state_result;
rc = 0;
} else if (mysql_field_count(mp->mysql)) {
i_modsql_error("cannot store result: %s",
mysql_error(mp->mysql));
conn->state = state_error;
rc = 1;
} else
rc = 0;
break;
}
return rc;
}
static int
s_mysql_free_result(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
mysql_free_result(mp->result);
mp->result = NULL;
return 0;
}
static unsigned
s_mysql_num_fields(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
return mysql_num_fields(mp->result);
}
static unsigned
s_mysql_num_tuples(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
return mysql_num_rows(mp->result);
}
static const char *
s_mysql_get_column(struct vmod_sql_connection *conn, size_t nrow, size_t ncol)
{
struct vmod_mysql_data *mp = conn->data;
MYSQL_ROW row;
if (nrow >= mysql_num_rows(mp->result) ||
ncol >= mysql_num_fields (mp->result))
return NULL;
mysql_data_seek(mp->result, nrow);
row = mysql_fetch_row(mp->result);
if (!row)
return NULL;
return row[ncol];
}
static char *
s_mysql_escape(struct vmod_sql_connection *conn, const char *arg)
{
struct vmod_mysql_data *mp = conn->data;
size_t len, size;
char *p;
switch (conn->state) {
case state_connected:
case state_result:
break;
case state_error:
case state_init:
if (i_modsql_connect(conn))
return NULL;
break;
default:
return NULL;
}
len = strlen(arg);
size = 2 * len + 1;
if (mp->bufsize < size) {
p = realloc(mp->buffer, size);
if (!p) {
i_modsql_error("not enough memory");
return NULL;
}
mp->buffer = p;
mp->bufsize = size;
}
mysql_real_escape_string(mp->mysql, mp->buffer, arg, len);
p = strdup(mp->buffer);
if (!p)
i_modsql_error("not enough memory");
return p;
}
long
s_mysql_affected_rows(struct vmod_sql_connection *conn)
{
struct vmod_mysql_data *mp = conn->data;
return mysql_affected_rows(mp->mysql);
}
struct vmod_sql_backend i_modsql_mysql_backend = {
"mysql",
s_mysql_init,
s_mysql_destroy,
s_mysql_connect,
s_mysql_disconnect,
s_mysql_escape,
s_mysql_query,
s_mysql_num_tuples,
s_mysql_num_fields,
s_mysql_free_result,
s_mysql_get_column,
s_mysql_affected_rows
};