428 lines
10 KiB
C
428 lines
10 KiB
C
/*
|
|
* Copyright (C) 2012 - Juan Ferrer Toribio
|
|
*
|
|
* This program 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 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "db-mysql.h"
|
|
#include <mysql/errmsg.h>
|
|
#include <mysql/mysqld_error.h>
|
|
#include <sys/socket.h>
|
|
#include <stdlib.h>
|
|
|
|
/**
|
|
* SECTION: db-mysql
|
|
* @Short_description: manages a connection to a PostgreSQL database.
|
|
* @Title: DbMysql
|
|
* @See_also: #DbConn
|
|
*
|
|
* This class manages a connection to a MySQL database internally. This
|
|
* is accessed through the #DbConn class to internally connect, query and
|
|
* disconnect the database.
|
|
**/
|
|
G_DEFINE_TYPE (DbMysql, db_mysql, DB_TYPE_PLUGIN);
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Private
|
|
|
|
static void db_mysql_close (DbMysql * obj)
|
|
{
|
|
if (obj->mysql)
|
|
{
|
|
g_free (obj->host);
|
|
g_free (obj->user);
|
|
g_free (obj->pass);
|
|
obj->host = NULL;
|
|
obj->user = NULL;
|
|
obj->pass = NULL;
|
|
|
|
mysql_close (obj->mysql);
|
|
obj->mysql = NULL;
|
|
obj->thread_id = 0;
|
|
obj->socket = -1;
|
|
obj->is_open = FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean db_mysql_open (DbMysql * obj, const gchar * host,
|
|
const gchar * schema, const gchar * user, const gchar * pass, GError ** err)
|
|
{
|
|
if (!obj->mysql)
|
|
obj->mysql = mysql_init (NULL);
|
|
|
|
if (!obj->mysql)
|
|
{
|
|
g_set_error (err
|
|
,DB_CONN_LOG_DOMAIN
|
|
,DB_CONN_ERROR_OPENING
|
|
,_("Can't allocate the needed memory")
|
|
);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!mysql_real_connect (obj->mysql, host, user, pass, schema, 0, NULL, CLIENT_MULTI_STATEMENTS))
|
|
{
|
|
gint error_code;
|
|
|
|
switch (mysql_errno (obj->mysql))
|
|
{
|
|
case ER_ACCESS_DENIED_ERROR:
|
|
error_code = DB_CONN_ERROR_BAD_LOGIN;
|
|
break;
|
|
default:
|
|
error_code = DB_CONN_ERROR_OPENING;
|
|
}
|
|
|
|
g_set_error (err
|
|
,DB_CONN_ERROR_OPENING
|
|
,error_code
|
|
,"%s", mysql_error (obj->mysql)
|
|
);
|
|
db_mysql_close (obj);
|
|
return FALSE;
|
|
}
|
|
|
|
obj->host = g_strdup (host);
|
|
obj->user = g_strdup (user);
|
|
obj->pass = g_strdup (pass);
|
|
obj->socket = obj->mysql->net.fd;
|
|
obj->thread_id = mysql_thread_id (obj->mysql);
|
|
obj->is_open = TRUE;
|
|
|
|
mysql_set_character_set (obj->mysql, "utf8");
|
|
return TRUE;
|
|
}
|
|
|
|
static void db_mysql_set_ssl (DbMysql * obj, const gchar * ca)
|
|
{
|
|
if (!obj->mysql)
|
|
obj->mysql = mysql_init (NULL);
|
|
|
|
mysql_ssl_set (obj->mysql, NULL, NULL, ca, NULL, NULL);
|
|
}
|
|
|
|
static void db_mysql_set_value (GValue * value, GType type, const gchar * string, gsize len)
|
|
{
|
|
if (!string)
|
|
{
|
|
g_value_init (value, GVN_TYPE_NULL);
|
|
return;
|
|
}
|
|
|
|
g_value_init (value, type);
|
|
|
|
switch (type)
|
|
{
|
|
case G_TYPE_BOOLEAN:
|
|
g_value_set_boolean (value, atoi (string));
|
|
break;
|
|
case G_TYPE_CHAR:
|
|
g_value_set_schar (value, atoi (string));
|
|
break;
|
|
case G_TYPE_INT:
|
|
g_value_set_int (value, atoi (string));
|
|
break;
|
|
case G_TYPE_UINT:
|
|
g_value_set_int (value, (guint) atoi (string));
|
|
break;
|
|
case G_TYPE_LONG:
|
|
g_value_set_long (value, g_ascii_strtoll (string, NULL, 0));
|
|
break;
|
|
case G_TYPE_ULONG:
|
|
g_value_set_ulong (value, g_ascii_strtoull (string, NULL, 0));
|
|
break;
|
|
case G_TYPE_FLOAT:
|
|
g_value_set_float (value, atof (string));
|
|
break;
|
|
case G_TYPE_DOUBLE:
|
|
g_value_set_double (value, g_ascii_strtod (string, NULL));
|
|
break;
|
|
case G_TYPE_STRING:
|
|
g_value_set_string (value, string);
|
|
break;
|
|
default:
|
|
if (type == G_TYPE_DATE)
|
|
{
|
|
GDate * date = NULL;
|
|
|
|
if (len >= 10)
|
|
{
|
|
GDateDay d = atoi (string + 8);
|
|
GDateMonth m = atoi (string + 5);
|
|
GDateYear y = atoi (string);
|
|
|
|
if (g_date_valid_dmy (d, m, y))
|
|
date = g_date_new_dmy (d, m, y);
|
|
}
|
|
|
|
g_value_take_boxed (value, date);
|
|
}
|
|
else if (type == G_TYPE_DATE_TIME)
|
|
{
|
|
GDateTime * date_time = NULL;
|
|
|
|
if (len >= 19)
|
|
date_time = g_date_time_new_local (
|
|
atoi (string)
|
|
,atoi (string + 5)
|
|
,atoi (string + 8)
|
|
,atoi (string + 11)
|
|
,atoi (string + 14)
|
|
,atoi (string + 17)
|
|
);
|
|
|
|
g_value_take_boxed (value, date_time);
|
|
}
|
|
else if (type == G_TYPE_BYTES)
|
|
g_value_take_boxed (value, g_bytes_new (string, len));
|
|
else
|
|
g_warning ("DbMysql: Can't handle this value type: %s",
|
|
g_type_name (type));
|
|
}
|
|
}
|
|
|
|
static DbResultSet * db_mysql_query (DbMysql * obj, const gchar * sql, GError ** err)
|
|
{
|
|
gint i, j;
|
|
guint errno = 0;
|
|
gulong * lengths;
|
|
GValue def = {0};
|
|
MYSQL_ROW myrow;
|
|
MYSQL_RES * res;
|
|
MYSQL_FIELD * field;
|
|
DbRow * row;
|
|
DbColumn * column;
|
|
DbResult * result;
|
|
DbResultSet * set = db_result_set_new ();
|
|
|
|
if (!mysql_query (obj->mysql, sql))
|
|
do {
|
|
if ((res = mysql_store_result (obj->mysql)))
|
|
{
|
|
result = g_new (DbResult, 1);
|
|
result->nrows = mysql_num_rows (res);
|
|
result->ncols = mysql_num_fields (res);
|
|
result->column = g_new (DbColumn, result->ncols);
|
|
result->data = g_ptr_array_new_full (result->nrows + 1,
|
|
(GDestroyNotify) db_row_free);
|
|
set->results = g_slist_append (set->results, result);
|
|
|
|
field = mysql_fetch_fields (res);
|
|
|
|
GType gtypes[result->ncols];
|
|
|
|
for (i = 0; i < result->ncols; i++)
|
|
{
|
|
column = &result->column[i];
|
|
column->info = 0;
|
|
column->name = g_strdup (field[i].org_name);
|
|
column->display = g_strdup (field[i].name);
|
|
column->table = g_strdup (field[i].org_table);
|
|
|
|
switch (field[i].type)
|
|
{
|
|
case MYSQL_TYPE_TINY:
|
|
case MYSQL_TYPE_INT24:
|
|
case MYSQL_TYPE_LONG:
|
|
case MYSQL_TYPE_SHORT:
|
|
case MYSQL_TYPE_YEAR:
|
|
gtypes[i] = G_TYPE_INT;
|
|
break;
|
|
case MYSQL_TYPE_LONGLONG:
|
|
gtypes[i] = G_TYPE_LONG;
|
|
break;
|
|
case MYSQL_TYPE_FLOAT:
|
|
gtypes[i] = G_TYPE_FLOAT;
|
|
break;
|
|
case MYSQL_TYPE_DOUBLE:
|
|
gtypes[i] = G_TYPE_DOUBLE;
|
|
break;
|
|
case MYSQL_TYPE_DATE:
|
|
gtypes[i] = G_TYPE_DATE;
|
|
break;
|
|
case MYSQL_TYPE_DATETIME:
|
|
case MYSQL_TYPE_TIMESTAMP:
|
|
gtypes[i] = G_TYPE_DATE_TIME;
|
|
break;
|
|
case MYSQL_TYPE_BLOB:
|
|
gtypes[i] = G_TYPE_BYTES;
|
|
break;
|
|
case MYSQL_TYPE_STRING:
|
|
case MYSQL_TYPE_DECIMAL:
|
|
case MYSQL_TYPE_NEWDECIMAL:
|
|
case MYSQL_TYPE_VAR_STRING:
|
|
case MYSQL_TYPE_TIME:
|
|
case MYSQL_TYPE_SET:
|
|
case MYSQL_TYPE_ENUM:
|
|
case MYSQL_TYPE_NULL:
|
|
default:
|
|
gtypes[i] = G_TYPE_STRING;
|
|
}
|
|
|
|
if (field[i].flags & AUTO_INCREMENT_FLAG)
|
|
{
|
|
SqlFunction * func = sql_function_new ("LAST_INSERT_ID", NULL);
|
|
|
|
g_value_init (&def, SQL_TYPE_FUNCTION);
|
|
g_value_take_object (&def, g_object_ref_sink (func));
|
|
}
|
|
else
|
|
db_mysql_set_value (&def, gtypes[i], field[i].def, field[i].def_length);
|
|
|
|
column->spec = gvn_param_spec_new_with_attrs (gtypes[i],
|
|
FALSE, !(field[i].flags & NOT_NULL_FLAG), &def);
|
|
g_value_unset (&def);
|
|
|
|
if (field[i].flags & PRI_KEY_FLAG)
|
|
column->info |= DB_COLUMN_PRI_KEY;
|
|
}
|
|
|
|
for (i = 0; (myrow = mysql_fetch_row (res)); i++)
|
|
{
|
|
lengths = mysql_fetch_lengths (res);
|
|
row = db_row_new (result->ncols, i);
|
|
g_ptr_array_add (result->data, row);
|
|
|
|
for (j = 0; j < result->ncols; j++)
|
|
db_mysql_set_value (&row->value[j], gtypes[j], myrow[j], lengths[j]);
|
|
}
|
|
|
|
mysql_free_result (res);
|
|
}
|
|
else if (mysql_field_count (obj->mysql) == 0)
|
|
{
|
|
result = g_new (DbResult, 1);
|
|
result->nrows = mysql_affected_rows (obj->mysql);
|
|
result->ncols = 0;
|
|
result->column = NULL;
|
|
result->data = NULL;
|
|
set->results = g_slist_append (set->results, result);
|
|
}
|
|
}
|
|
while (mysql_more_results (obj->mysql) && !mysql_next_result (obj->mysql));
|
|
|
|
if ((errno = mysql_errno (obj->mysql)))
|
|
{
|
|
switch (errno)
|
|
{
|
|
case CR_SERVER_LOST:
|
|
case CR_SERVER_GONE_ERROR:
|
|
errno = DB_CONN_ERROR_LOST;
|
|
break;
|
|
case CR_OUT_OF_MEMORY:
|
|
case CR_COMMANDS_OUT_OF_SYNC:
|
|
errno = DB_CONN_ERROR_QUERY_FATAL;
|
|
break;
|
|
case CR_UNKNOWN_ERROR:
|
|
default:
|
|
errno = DB_CONN_ERROR_UNKNOW;
|
|
}
|
|
|
|
g_set_error (err
|
|
,DB_CONN_LOG_DOMAIN
|
|
,errno
|
|
,"%s", mysql_error (obj->mysql)
|
|
);
|
|
db_result_set_free (set);
|
|
set = NULL;
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
static void db_mysql_kill_query (DbMysql * obj)
|
|
{
|
|
if (obj->mysql)
|
|
{
|
|
gboolean killed = FALSE;
|
|
MYSQL * new_conn = mysql_init (NULL);
|
|
|
|
if (new_conn
|
|
&& mysql_real_connect (new_conn, obj->host, obj->user, obj->pass, NULL, 0, NULL, 0))
|
|
{
|
|
gchar * sql = g_strdup_printf ("KILL QUERY %lu", obj->thread_id);
|
|
|
|
if (!mysql_query (new_conn, sql))
|
|
{
|
|
MYSQL_RES * res = mysql_store_result (new_conn);
|
|
mysql_free_result (res);
|
|
killed = TRUE;
|
|
}
|
|
|
|
g_free (sql);
|
|
}
|
|
if (new_conn)
|
|
{
|
|
if (mysql_errno (new_conn))
|
|
g_warning ("DbMysql: %s", mysql_error (new_conn));
|
|
|
|
mysql_close (new_conn);
|
|
}
|
|
|
|
if (!killed && obj->socket != -1 && !shutdown (obj->socket, SHUT_RDWR))
|
|
obj->socket = -1;
|
|
}
|
|
}
|
|
|
|
static void db_mysql_value_render (SqlValue * obj, SqlRender * render)
|
|
{
|
|
if (G_VALUE_TYPE (obj->value) == G_TYPE_BYTES)
|
|
{
|
|
gsize to_size;
|
|
gsize from_size;
|
|
GString * buffer = render->buffer;
|
|
GBytes * bin = g_value_get_boxed (obj->value);
|
|
const gchar * from = g_bytes_get_data (bin, &from_size);
|
|
|
|
sql_render_add_espace (render);
|
|
sql_render_append (render, "'");
|
|
g_string_set_size (buffer, buffer->len + (from_size * 2 + 1));
|
|
to_size = mysql_real_escape_string (
|
|
DB_MYSQL (render->data)->mysql,
|
|
&(buffer->str[buffer->len]), from, from_size
|
|
);
|
|
buffer->len += to_size;
|
|
sql_render_append (render, "'");
|
|
}
|
|
else
|
|
sql_object_render (SQL_OBJECT (obj), render);
|
|
}
|
|
|
|
//+++++++++++++++++++++++++++++++++++++++++++++++++++ Class
|
|
|
|
static void db_mysql_init (DbMysql * obj)
|
|
{
|
|
SqlRender * render = sql_render_new ('`');
|
|
sql_render_register_function (render, SQL_TYPE_VALUE,
|
|
(SqlRenderFunc) db_mysql_value_render);
|
|
|
|
DB_PLUGIN (obj)->render = render;
|
|
obj->mysql = NULL;
|
|
obj->host = NULL;
|
|
obj->user = NULL;
|
|
obj->pass = NULL;
|
|
obj->thread_id = 0;
|
|
obj->socket = -1;
|
|
obj->is_open = FALSE;
|
|
}
|
|
|
|
static void db_mysql_class_init (DbMysqlClass * k)
|
|
{
|
|
DbPluginClass * klass = DB_PLUGIN_CLASS (k);
|
|
klass->open = (DbPluginOpenFunc) db_mysql_open;
|
|
klass->close = (DbPluginCloseFunc) db_mysql_close;
|
|
klass->set_ssl = (DbPluginSetSSL) db_mysql_set_ssl;
|
|
klass->query = (DbPluginQueryFunc) db_mysql_query;
|
|
klass->kill_query = (DbPluginKillQueryFunc) db_mysql_kill_query;
|
|
}
|