#include "mod_mysql.h"

static size_t string_apply_maxlen (const char * const string, const size_t len, const size_t maxlen)
{
	static const uint8_t numbytes_utf8[256] = {
		1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
		1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
		1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
		1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
		1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
		1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
		2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
		3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,5,5,5,5,6,6,6,6
	};

	size_t i = 0;
	size_t chars = 0;
	do {
		uint8_t	numbytes = numbytes_utf8[(uint8_t)string[i]];

		i += numbytes;

		if (i > len)
			return 0;

		if (++chars == maxlen)
			return i;
	} while (i != len);

	return i;
}

static char *strdup2 (const char *s)
{
	if (s == NULL)
		return NULL;

	return strdup (s);
}

static void parse_connectionstring (mod_t *handle)
{
	char *cs = strdup (handle->cfg->connectionstring);
	char *s = cs;

	for (;;) {
		while (isspace ((unsigned char)*s) != 0)
			s++;

		char *keyword, *d;
		for (keyword = d = s; *s != '\0'; s++) {
			if (*s == '=') {
				if (s[1] == '=') {
					*d++ = *s++;
					continue;
				}

				break;
			}

			*d++ = *s;
		}

		if (*s == '\0')
			break;

		char *e = d - 1;

		while (isspace ((unsigned char)*e) != 0 && e > keyword)
			e--;

		e[1] = '\0';
		s++;

		while (isspace ((unsigned char)*s) != 0)
			s++;

		char enclosed = '\0';

		if (*s == '\'' || *s == '\"') {
			enclosed = *s++;
		} else if (*s == '{') {
			s++;
			enclosed = '}';
		}

		char *cutoff = keyword;

		char *value;
		for (value = d = s; *s != '\0'; s++) {
			if (*s == enclosed) {
				if (s[1] == enclosed) {
					*d++ = *s++;
					continue;
				}

				enclosed = '\0';
				cutoff = s;
				continue;
			}

			if (*s == ';' && enclosed == '\0') {
				s++;
				break;
			}

			*d++ = *s;
		}

		e = d - 1;

		while (isspace ((unsigned char)*e) != 0 && e > cutoff)
			e--;

		e[1] = '\0';

		if (*value == '\0')
			value = NULL;

		if (     strcasecmp (keyword, "host") == 0 || strcasecmp (keyword, "server") == 0)
			handle->cfg->host = strdup2 (value);
		else if (strcasecmp (keyword, "port") == 0)
			handle->cfg->port = strdup2 (value);
		else if (strcasecmp (keyword, "dbname") == 0 || strcasecmp (keyword, "database") == 0 || strcasecmp (keyword, "data source") == 0 || strcasecmp (keyword, "datasource") == 0)
			handle->cfg->database = strdup2 (value);
		else if (strcasecmp (keyword, "user") == 0     || strcasecmp (keyword, "uid") == 0 || strcasecmp (keyword, "user id") == 0 || strcasecmp (keyword, "user name") == 0 || strcasecmp (keyword, "username") == 0)
			handle->cfg->username = strdup2 (value);
		else if (strcasecmp (keyword, "password") == 0 || strcasecmp (keyword, "pwd") == 0)
			handle->cfg->password = strdup2 (value);
		else if (strcasecmp (keyword, "sslkey") == 0)
			handle->cfg->ssl_key = strdup2 (value);
		else if (strcasecmp (keyword, "sslcert") == 0)
			handle->cfg->ssl_cert = strdup2 (value);
		else if (strcasecmp (keyword, "sslrootcert") == 0)
			handle->cfg->ssl_ca = strdup2 (value);
		else if (strcasecmp (keyword, "sslcapath") == 0)
			handle->cfg->ssl_capath = strdup2 (value);
		else if (strcasecmp (keyword, "sslcipher") == 0)
			handle->cfg->ssl_cipher = strdup2 (value);
		else if (strcasecmp (keyword, "sslcrl") == 0)
			handle->ssl_crl = strdup2 (value);
		else if (strcasecmp (keyword, "sslcrlpath") == 0)
			handle->ssl_crlpath = strdup2 (value);
		else if (strcasecmp (keyword, "sslmode") == 0)
			handle->ssl_mode = strdup2 (value);
		else if ((strcasecmp (keyword, "connection timeout") == 0 || strcasecmp (keyword, "connect_timeout") == 0) && value != NULL)
			handle->timeout_connection = strtoul (value, NULL, 10);
		else if ((strcasecmp (keyword, "write timeout") == 0 || strcasecmp (keyword, "write_timeout") == 0) && value != NULL)
			handle->timeout_write = strtoul (value, NULL, 10);

		keyword = s;
	}

	free (cs);
}

int mod_init ()
{
	mysql_library_init(0,NULL,NULL);
	return 1;
}

int mod_exit()
{
	mysql_library_end();
	return 1;
}

void *mod_create (mod_config *cfg)
{
	mod_t *_handle = calloc (1, sizeof (mod_t ));

	if (_handle == NULL)
		return NULL;

	_handle->string  = malloc (1024);
	_handle->cfg  = cfg;
	_handle->stringlen  = 1024;

	if (_handle->string == NULL) {
		free (_handle);
		return NULL;
	}

	/* if we have a connection string it overwrites every other setting */
	if (cfg->connectionstring != NULL)
		parse_connectionstring (_handle);

	if (cfg->port != NULL) {
		if (cfg->port[0] == '/') {
			_handle->port = 0;
			_handle->unix_socket = cfg->port;
		} else {
			_handle->port = strtoul (cfg->port, NULL, 10);
			_handle->unix_socket = NULL;
		}
	} else {
		_handle->port = 0;
		_handle->unix_socket = NULL;
	}

	return _handle;
}

int mod_destroy (void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return -1;

	free (_handle->ssl_crl);
	free (_handle->ssl_crlpath);
	free (_handle->string);
	free (_handle);
	return 1;
}

int mod_connect (void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return 0;

	_handle->lasterrormessage = NULL;
	_handle->lasterrno = 0;
	_handle->mysqlhandle = mysql_init (NULL);

	char opt = 1;
	mysql_options (_handle->mysqlhandle, MYSQL_OPT_RECONNECT, (char *) &opt);
	mysql_options (_handle->mysqlhandle, MYSQL_READ_DEFAULT_GROUP, "mda");

# ifdef MYSQL_OPT_SSL_MODE
	if (_handle->ssl_mode != NULL) {
		unsigned int opt;

		char *endp;
		opt = strtol (_handle->ssl_mode, &endp, 10);

		if (opt == 0 && *endp != '\0') {
			if (strcasecmp (_handle->ssl_mode, "DISABLED") == 0)
				opt = SSL_MODE_DISABLED;
			else if (strcasecmp (_handle->ssl_mode, "NONE") == 0)
				opt = SSL_MODE_DISABLED;
			else if (strcasecmp (_handle->ssl_mode, "PREFERRED") == 0)
				opt = SSL_MODE_PREFERRED;
			else if (strcasecmp (_handle->ssl_mode, "REQUIRED") == 0)
				opt = SSL_MODE_REQUIRED;
			else if (strcasecmp (_handle->ssl_mode, "VERIFY_CA") == 0)
				opt = SSL_MODE_VERIFY_CA;
			else if (strcasecmp (_handle->ssl_mode, "VERIFY_IDENTITY") == 0)
				opt = SSL_MODE_VERIFY_IDENTITY;
		}

		mysql_options (_handle->mysqlhandle, MYSQL_OPT_SSL_MODE, &opt);
	}
# else
	if (_handle->ssl_mode != NULL) {
		int8_t opt_enable = 1, opt_disable = 0;

		char *endp;
		opt = strtol (_handle->ssl_mode, &endp, 10);

		if (opt == 0 && *endp != '\0') {
			if (strcasecmp (_handle->ssl_mode, "DISABLED") == 0)
				mysql_options (_handle->mysqlhandle, MYSQL_OPT_SSL_ENFORCE, &opt_disable);
			else if (strcasecmp (_handle->ssl_mode, "NONE") == 0)
				mysql_options (_handle->mysqlhandle, MYSQL_OPT_SSL_ENFORCE, &opt_disable);
			else if (strcasecmp (_handle->ssl_mode, "REQUIRED") == 0)
				mysql_options (_handle->mysqlhandle, MYSQL_OPT_SSL_ENFORCE, &opt_enable);
			else if (strcasecmp (_handle->ssl_mode, "VERIFY_CA") == 0)
				mysql_options (_handle->mysqlhandle, MYSQL_OPT_SSL_ENFORCE, &opt_enable);
			else if (strcasecmp (_handle->ssl_mode, "VERIFY_IDENTITY") == 0)
				mysql_options (_handle->mysqlhandle, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &opt_enable);
		}
	}
# endif

	if (_handle->cfg->ssl_key != NULL || _handle->cfg->ssl_cert != NULL || _handle->cfg->ssl_ca != NULL || _handle->cfg->ssl_capath != NULL || _handle->cfg->ssl_cipher != NULL)
		mysql_ssl_set (_handle->mysqlhandle, _handle->cfg->ssl_key, _handle->cfg->ssl_cert, _handle->cfg->ssl_ca, _handle->cfg->ssl_capath, _handle->cfg->ssl_cipher);

	if (_handle->timeout_connection != 0)
		mysql_options (_handle->mysqlhandle, MYSQL_OPT_CONNECT_TIMEOUT, &_handle->timeout_connection);

	if (_handle->timeout_write != 0)
		mysql_options (_handle->mysqlhandle, MYSQL_OPT_WRITE_TIMEOUT, &_handle->timeout_write);

	if (mysql_real_connect ( _handle->mysqlhandle, _handle->cfg->host, _handle->cfg->username, _handle->cfg->password, _handle->cfg->database, _handle->port, _handle->unix_socket, CLIENT_FOUND_ROWS) == NULL) {
		_handle->lasterrno = mysql_errno (_handle->mysqlhandle);
		_handle->lasterrormessage = mysql_error ( _handle->mysqlhandle );
		return 0;
	}

	if (mysql_set_character_set ( _handle->mysqlhandle, "utf8" ) != 0) {
		_handle->lasterrno = mysql_errno (_handle->mysqlhandle);
		_handle->lasterrormessage = mysql_error ( _handle->mysqlhandle ); 
		return 0;
	}

	if (_handle->status != MOD_STATUS_SHUTDOWN)
		_handle->status = MOD_STATUS_OK;

	return 1;
}

int mod_disconnect (void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL || _handle->mysqlhandle == NULL)
		return 0;

	mysql_close ( _handle->mysqlhandle );
	_handle->mysqlhandle = NULL;
	return 1;
}

int mod_getlasterror (void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return -1;

	return _handle->lasterrno;
}

char *mod_geterrorstring (void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return NULL;

	return (char*)_handle->lasterrormessage;
}

void mod_reconnect(void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return;

	mod_disconnect(_handle);

	if (_handle->status == MOD_STATUS_SHUTDOWN)
		return;

	_handle->status = MOD_STATUS_DISCONNECTED;

	do {
		mod_disconnect(_handle);
#ifndef WIN32
		sleep(1);
#else
		Sleep(1000);
#endif
		mod_connect(_handle);
	} while (_handle->status == MOD_STATUS_DISCONNECTED);

	return;
}

void mod_freeresult (void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return;

	while (_handle->mod_result_head != NULL) {
		mod_result *next = _handle->mod_result_head->next;

		free (_handle->mod_result_head->columnvalue);
		free (_handle->mod_result_head);

		_handle->mod_result_head = next;
	}

	_handle->mod_result_tail = NULL;
}

void *mod_querysql(void *handle,char *_query,int *returncode)
{
	mod_t	*_handle = (mod_t*) handle;
	MYSQL_RES *_result;
	MYSQL_ROW row;
	int _numfields = 0;
	int i = 0;

	if (_handle == NULL)
		return NULL;

	_handle->lasterrormessage = NULL;
	*returncode = 0;
	if ( 0 != mysql_query ( _handle->mysqlhandle, _query ) ) {
			_handle->lasterrormessage = mysql_error ( _handle->mysqlhandle );
			_handle->lasterrno = mysql_errno( _handle->mysqlhandle );

			switch(_handle->lasterrno) {
				case 1205: // Lock wait timeout exceeded; try restarting transaction
				case 1213: // Deadlock found when trying to get lock; try restarting transaction
				case 1402: // Transaction branch was rolled back
				case 1613: // Transaction branch was rolled back: took too long
				case 1614: // Transaction branch was rolled back: deadlock was detected
				case 1622: // Storage engine %s does not support rollback for this statement. Transaction rolled back and must be restarted
				case 3058: // Deadlock found when trying to get user-level lock; try rolling back transaction/releasing locks and restarting lock acquisition.
				case 3101: // Plugin instructed the server to rollback the current transaction.
				case 3132: // Deadlock found when trying to get locking service lock; try releasing locks and restarting lock acquisition.
					*returncode = -3;
					return NULL;

				case 1400: // The XA transaction is in a wrong state
					*returncode = -5;
					return NULL;

				case 1792: // The server is set to READ ONLY
				case 2002: // Connection Error
				case 2003: // Connection Refused
				case 2006: // MySQL server has gone away 
				case 2013: // Lost connection to MySQL server during query 
				case 2055: // Lost connection to MySQL server at '%s', system error: %d 
				case 2014: // Commands out of sync; you can't run this command now 
				case 2048: // Invalid connection handle 
					*returncode = -2;
					return NULL;	//?

				default:
					*returncode = -4;
					return NULL;
			}
	}
	_result = mysql_store_result(_handle->mysqlhandle);
	_numfields = mysql_num_fields(_result);

	mod_freeresult(_handle);
	
	while((row = mysql_fetch_row(_result))) {
		for(i = 0; i < _numfields; i++) {
			if(_handle->mod_result_tail == NULL ) {
				_handle->mod_result_tail = calloc(1,sizeof(mod_result));
				_handle->mod_result_head = _handle->mod_result_tail;
			}
			else {
				_handle->mod_result_tail->next = calloc(1,sizeof(mod_result));
				_handle->mod_result_tail = _handle->mod_result_tail->next;
		
			}
			_handle->mod_result_tail->columnvalue = row[i] == NULL ? NULL: strdup(row[i]);
		}
	}
	mysql_free_result(_result);
	return _handle->mod_result_head;
}

int mod_executesql(void	*handle, char *_queary)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return -1;
	_handle->lasterrormessage = NULL;
	if ( 0 != mysql_query ( _handle->mysqlhandle, _queary ) ) {
			_handle->lasterrormessage = mysql_error ( _handle->mysqlhandle );
			_handle->lasterrno = mysql_errno( _handle->mysqlhandle );

			switch(_handle->lasterrno) {
				case 1022: // Duplicate Key
				case 1062: // Duplicate Entry
					return -1;

				case 1205: // Lock wait timeout exceeded; try restarting transaction
				case 1213: // Deadlock found when trying to get lock; try restarting transaction
				case 1402: // Transaction branch was rolled back
				case 1613: // Transaction branch was rolled back: took too long
				case 1614: // Transaction branch was rolled back: deadlock was detected
				case 1622: // Storage engine %s does not support rollback for this statement. Transaction rolled back and must be restarted
				case 3058: // Deadlock found when trying to get user-level lock; try rolling back transaction/releasing locks and restarting lock acquisition.
				case 3101: // Plugin instructed the server to rollback the current transaction.
				case 3132: // Deadlock found when trying to get locking service lock; try releasing locks and restarting lock acquisition.
					return -3;

				case 1400: // The XA transaction is in a wrong state
					return -5;

				case 1792: // The server is set to READ ONLY
				case 2002: // Connection Error
				case 2003: // Connection Refused
				case 2013: // Lost connection to MySQL server during query 
				case 2055: // Lost connection to MySQL server at '%s', system error: %d 
				case 2014: // Commands out of sync; you can't run this command now 
				case 2048: // Invalid connection handle 
					return -2;

				case 2006: // MySQL server has gone away 
					if (strlen (_queary) > 1024*1024) /* could be due to 'too large a message' so we return -4 here to ignore it and then hope that the next one will get -2 to do the reconnect */
						return -4;
					else
						return -2;

				default:
					return -4;
			}
	}

	const unsigned long long affected = mysql_affected_rows ( _handle->mysqlhandle );

	if (affected == 0) {
		_handle->lasterrormessage = "Affected rows:0";
		_handle->lasterrno = 0;
	}

	return affected;
}

char *mod_add_value (void *handle, char *_value, size_t maxlen)
{
	mod_t *_handle = (mod_t*) handle;
	size_t _len;

	if (_handle == NULL || _value == NULL || _handle->mysqlhandle == NULL)
		return NULL;

	_len = strlen (_value);

	if (maxlen != 0 && _len > maxlen)
		_len = string_apply_maxlen (_value, _len, maxlen);

	if (_len * 2 + 4 > _handle->stringlen ) {
		_handle->stringlen = _len * 2 + 4;
		_handle->stringlen += 1024 - (_handle->stringlen % 1024);
		free (_handle->string);
		_handle->string = malloc (_handle->stringlen);
	}

	_handle->string[0] ='\'';
	_len = mysql_real_escape_string ( _handle->mysqlhandle, _handle->string + 1, _value, _len);
	_handle->string[_len+1] = '\'';
	_handle->string[_len+2] = '\0';

	return _handle->string;
}

uint8_t mod_get_status (void *handle)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return 0;

	return	_handle->status;
}

void mod_set_status (void *handle, uint8_t status)
{
	mod_t *_handle = (mod_t*) handle;

	if (_handle == NULL)
		return;

	_handle->status = status;
	return;
}

uint8_t mod_add_column (void *handle, char *tablename, char *columnname, char *type, char *len, uint8_t nullable, int _unsigned, int command)
{
	mod_t *_handle = (mod_t*) handle;
	size_t pos = 0;	

	if (_handle == NULL)
		return 0;

	if (command == 0)
		pos = sprintf(_handle->string ,"ALTER TABLE %s ADD COLUMN %s ",tablename,columnname);
	else if (command == 1)
		pos = sprintf(_handle->string ,"ALTER TABLE %s MODIFY COLUMN %s ",tablename,columnname);

	if(type == NULL)
		return 0;

	pos += sprintf(_handle->string + pos, "%s",type);

	/* fractional seconds was added in v5.6.4 of MySQL */
	if (strcmp (type, "time") == 0 && len != NULL && _handle->cfg->version < 50604)
		len = NULL;

	if(len != NULL && 0 != strtoul(len,NULL,10))
		pos += sprintf(_handle->string + pos ,"(%s)",len);

	if(_unsigned != 0)
		pos += sprintf(_handle->string + pos, " UNSIGNED");

	sprintf(_handle->string + pos, " %sNULL",nullable == 0 ? "NOT ":"");

	if (command == 0 || command == 1) {
		if (mod_executesql(handle,_handle->string) >= 0)
			return 1;
	} else if (command == 2 && tablename != NULL) {
		strcpy (tablename, _handle->string);
		return 1;
	}

	return 0;
}

int mod_get_info (void *handle, mod_info *modinfo, int modinfolen)
{
	char ver[256];
	mod_t *_handle	= (mod_t*) handle;
	int pos;

	if (_handle == NULL || modinfolen < (int)(sizeof (struct mod_info_s)))
		return -1;

	_handle->cfg->version = mysql_get_server_version(_handle->mysqlhandle);
	pos = sprintf(ver,"%ld.%ld.%ld", _handle->cfg->version/10000, (_handle->cfg->version/100) % 100, _handle->cfg->version % 100);

	modinfo->version = malloc (pos + 1);
	memcpy (modinfo->version, ver, pos + 1);

	modinfo->transaction = strdup ("START TRANSACTION");
	modinfo->upsert = 1;

	return 1;
}

Databaseinfo *mod_get_databaseinfo (void *_handle)
{
	static const char errormsg1[] = "out of memory";
	static const char errormsg2[] = "no rows returned from database";

	char *query;
	mod_t *handle = (mod_t *) _handle;
	char **tables = NULL;
	int tables_num = 0;
	Databaseinfo *head = NULL;
	Databaseinfo *tail = NULL;
	MYSQL_RES *result;
	MYSQL_ROW row;
	int i;
	size_t len;

	if (handle == NULL || handle->cfg == NULL || handle->cfg->database == NULL)
		return NULL;

	handle->lasterrormessage = NULL;
	handle->lasterrno = 0;
	len = strlen (handle->cfg->database) + 128;
	query = malloc (len);

	if (query == NULL) {
		handle->lasterrormessage = errormsg1;
		handle->lasterrno = 1;
		return NULL;
	}

	sprintf (query, "SHOW TABLES IN `%s`", handle->cfg->database);

	if (mysql_query (handle->mysqlhandle, query) != 0) {
		handle->lasterrormessage = mysql_error (handle->mysqlhandle);
		handle->lasterrno = mysql_errno (handle->mysqlhandle);
		free (query);
		return NULL;
	}

	result = mysql_store_result (handle->mysqlhandle);

	if (result == NULL) {
		handle->lasterrormessage = mysql_error (handle->mysqlhandle);
		handle->lasterrno = mysql_errno (handle->mysqlhandle);
		free (query);
		return NULL;
	}

	while ((row = mysql_fetch_row (result)) != NULL) {
		void *tmp;
		size_t tmplen;

		if (row[0] == NULL || row[0][0] == '\0')
			continue;

		tmp = realloc (tables, (tables_num + 1) * sizeof (char *));

		if (tmp == NULL) {
			handle->lasterrormessage = errormsg1;
			handle->lasterrno = 1;
			goto error_exit;
		}

		tables = (char **) tmp;
		tables[tables_num++] = strdup (row[0]);

		tmplen = strlen (row[0]);

		if (tmplen + 20 > len) {
			len = tmplen + 128;
			free (query);
			query = malloc (len);

			if (query == NULL) {
				handle->lasterrormessage = errormsg1;
				handle->lasterrno = 1;
				goto error_exit;
			}
		}
	}

	mysql_free_result (result);
	result = NULL;

	if (tables_num == 0) {
		handle->lasterrormessage = errormsg2;
		handle->lasterrno = 1;
		goto error_exit;
	}

	for (i = 0; i < tables_num; i++) {
		MYSQL_FIELD *fields;
		unsigned int fields_num;
		unsigned int j;
		unsigned int pos_field = 0, pos_type = 1, pos_null = 2;

		sprintf (query, "SHOW COLUMNS IN `%s`", tables[i]);

		if (mysql_query (handle->mysqlhandle, query) != 0) {
			handle->lasterrormessage = mysql_error (handle->mysqlhandle);
			handle->lasterrno = mysql_errno (handle->mysqlhandle);
			goto error_exit;
		}

		result = mysql_store_result (handle->mysqlhandle);

		if (result == NULL) {
			handle->lasterrormessage = mysql_error (handle->mysqlhandle);
			handle->lasterrno = mysql_errno (handle->mysqlhandle);
			goto error_exit;
		}

		fields = mysql_fetch_fields (result);
		fields_num = mysql_num_fields (result);

		for (j = 0; j < fields_num; j++) {
			if (strcmp (fields[j].name, "Field") == 0)
				pos_field = j;
			else if (strcmp (fields[j].name, "Type") == 0)
				pos_type = j;
			else if (strcmp (fields[j].name, "Null") == 0)
				pos_null = j;
		}

		while ((row = mysql_fetch_row (result)) != NULL) {
			Databaseinfo *di;

			if (row[pos_field] == NULL || row[pos_field][0] == '\0')
				continue;

			di = calloc (1, sizeof *di);

			if (di == NULL) {
				while (head != NULL) {
					di = head;
					head = di->next;

					free (di->tablename);
					free (di->fieldname);
					free (di);
				}

				handle->lasterrormessage = errormsg1;
				handle->lasterrno = 1;
				goto error_exit;
			}

			di->tablename = strdup (tables[i]);
			di->fieldname = strdup (row[pos_field]);

			if (row[pos_type] != NULL) {
				char *p = strstr (row[pos_type], "unsigned");

				if (p != NULL) {
					di->_unsigned = 1;
					*p = '\0';
				}

				p = strchr (row[pos_type], '(');

				if (p != NULL) {
					char *e = strchr (p, ')');

					*p++ = '\0';

					if (e != NULL) {
						*e = '\0';
						di->len = strdup (p);
					}
				}

				di->type = strdup (row[pos_type]);
			}

			if (row[pos_null] != NULL && strcmp (row[pos_null], "YES") == 0)
				di->nullable = 1;

			if (di->tablename == NULL || di->fieldname == NULL) {
				free (di->tablename);
				free (di->fieldname);
				free (di->type);
				free (di->len);

				while (head != NULL) {
					di = head;
					head = di->next;

					free (di->tablename);
					free (di->fieldname);
					free (di->type);
					free (di->len);
					free (di);
				}

				handle->lasterrormessage = errormsg1;
				handle->lasterrno = 1;
				goto error_exit;
			}

			if (head == NULL)
				head = di;
			else
				tail->next = di;

			di->previous = tail;
			tail = di;
		}

		mysql_free_result (result);
		result = NULL;
	}

error_exit :
	if (result != NULL)
		mysql_free_result (result);

	if (tables != NULL) {
		for (i = 0; i < tables_num; i++)
			free (tables[i]);

		free (tables);
	}

	if (query != NULL)
		free (query);

	if (handle->lasterrormessage != NULL) {
		mod_free_databaseinfo (head);
		return NULL;
	}

	return head;
}

void mod_free_databaseinfo (Databaseinfo *dbinfo)
{
	if (dbinfo == NULL)
		return;

	while(dbinfo != NULL) {
		Databaseinfo *next = dbinfo->next;

		free(dbinfo->tablename);
		free(dbinfo->fieldname);
		free(dbinfo->type);
		free(dbinfo->len);
		free(dbinfo);

		dbinfo = next;
	}
}
