/* * 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 . */ #include "db-mysql.h" #include #include #include #include /** * 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->alias = g_strdup (field[i].name); column->table = g_strdup (field[i].org_table); column->table_alias = g_strdup (field[i].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) { SqlObject * 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, SqlBatch * batch) { 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, batch); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ 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; }