diff options
Diffstat (limited to 'project/dclib/dclib-mysql.c')
| -rw-r--r-- | project/dclib/dclib-mysql.c | 1560 |
1 files changed, 1560 insertions, 0 deletions
diff --git a/project/dclib/dclib-mysql.c b/project/dclib/dclib-mysql.c new file mode 100644 index 0000000..e3ee297 --- /dev/null +++ b/project/dclib/dclib-mysql.c @@ -0,0 +1,1560 @@ + +/*************************************************************************** + * * + * _____ ____ * + * | __ \ / __ \ _ _ _____ * + * | | \ \ / / \_\ | | | | _ \ * + * | | \ \| | | | | | |_| | * + * | | | || | | | | | ___/ * + * | | / /| | __ | | | | _ \ * + * | |__/ / \ \__/ / | |___| | |_| | * + * |_____/ \____/ |_____|_|_____/ * + * * + * Wiimms source code library * + * * + *************************************************************************** + * * + * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> * + * * + *************************************************************************** + * * + * 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 2 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. * + * * + * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt * + * * + ***************************************************************************/ + +#define _GNU_SOURCE 1 + +#include <string.h> +#include <mysql/mysql.h> +#include <mysql/errmsg.h> + +#include "dclib-mysql.h" +#include "dclib-network.h" + +// http://dev.mysql.com/doc/refman/5.1/en/c-api-functions.html + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// struct MySql_t /////////////// +/////////////////////////////////////////////////////////////////////////////// +// management + +void InitializeMYSQL ( MySql_t *my ) +{ + #if DEBUG + static uint done = 0; + if (!done) + { + done++; + TRACE("- MYSQL\n"); + TRACE_SIZEOF(MySql_t); + TRACE_SIZEOF(MySqlResult_t); + TRACE_SIZEOF(MySqlStatus_t); + TRACE_SIZEOF(st_mysql); + TRACE_SIZEOF(st_mysql_res); + TRACE_SIZEOF(st_mysql_field); + } + #endif + + DASSERT(my); + memset(my,0,sizeof(*my)); + my->auto_reconnect = true; + my->prefix = EmptyString; + InitializeStatusMYSQL(&my->status); +} + +/////////////////////////////////////////////////////////////////////////////// + +void ResetMYSQL ( MySql_t *my ) +{ + DASSERT(my); + CloseMYSQL(my); + + FREE((char*)my->server); + FREE((char*)my->user); + FREE((char*)my->password); + FREE((char*)my->database); + FreeString(my->prefix); + + InitializeMYSQL(my); +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError OpenMYSQL ( MySql_t *my, uint loglevel ) +{ + DASSERT(my); + ClearStatusMYSQL(&my->status); + + if (my->mysql) + return ERR_OK; // already connected + + if ( !my || !my->server ) + { + if ( loglevel >= MYLL_ERROR ) + ERROR0(ERR_MISSING_PARAM,"No MySql server defined.\n"); + return ERR_MISSING_PARAM; + } + + NetworkHost_t host; + ResolveHost(&host,true,my->server,MYSQL_DEFAULT_PORT,false,true); + + PRINT("MYSQL CONNECT: %s:%u -> %s\n", + host.name, host.port, PrintIP4(0,0,host.ip4,host.port) ); + + static bool lib_init_done = false; + if (!lib_init_done) + { + noPRINT("mysql_library_init()\n"); + if (mysql_library_init(0,NULL,NULL)) + { + if ( loglevel >= MYLL_ERROR ) + ERROR0(ERR_DATABASE,"Can't initialize MySql library.\n"); + return ERR_DATABASE; + } + lib_init_done = true; + } + + my->mysql = mysql_init(NULL); + //HEXDUMP16(0,0,my->mysql,sizeof(*my->mysql)); + if (!my->mysql) + { + if ( loglevel >= MYLL_ERROR ) + ERROR0(ERR_DATABASE,"Can't initialize MySql data.\n"); + ResetHost(&host); + return ERR_DATABASE; + } + + if (my->auto_reconnect) + { + my_bool reconnect = 1; + mysql_options(my->mysql,MYSQL_OPT_RECONNECT,&reconnect); + } + + noPRINT("connect(%p,%s,%s,%s,%s,%u,,)\n", + my->mysql, host.name, my->user, my->password, my->database, host.port ); + MYSQL *stat = mysql_real_connect ( my->mysql, host.name, + my->user, my->password, my->database, + host.port, NULL, 0 ); + + if (!stat) + { + GetStatusMYSQL(&my->status,my->mysql); + if ( loglevel >= MYLL_ERROR ) + ERROR0(ERR_CANT_CONNECT,"Can't connect to %s:\n-> %s\n", + PrintIP4(0,0,host.ip4,host.port),my->status.message); + ResetHost(&host); + mysql_close(my->mysql); + my->mysql = 0; + return ERR_CANT_CONNECT; + } + + mysql_set_character_set(my->mysql,"utf8mb4"); + my->total_connect_count++; + ResetHost(&host); + return ERR_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +void CloseMYSQL ( MySql_t *my ) +{ + DASSERT(my); + + //--- close results + + FreeResultListMYSQL(my->first_result); + my->first_result = 0; + FreeResultListMYSQL(my->pool_result); + my->pool_result = 0; + + + //--- close database + + if (my->mysql) + { + mysql_close(my->mysql); + my->mysql = 0; + } + + + //--- reset status + + ClearStatusMYSQL(&my->status); +} + +/////////////////////////////////////////////////////////////////////////////// + +void DefineDatabase +( + MySql_t *my, // valid struct + ccp server, // NULL or server name + ccp user, // NULL or user name + ccp password, // NULL or user password + ccp database // NULL or name of database +) +{ + DASSERT(my); + CloseMYSQL(my); + + if (server) + { + FREE((char*)my->server); + my->server = STRDUP(server); + } + + if (user) + { + FREE((char*)my->user); + my->user = STRDUP(user); + } + + if (password) + { + FREE((char*)my->password); + my->password = STRDUP(password); + } + + if (database) + { + FREE((char*)my->database); + my->database = STRDUP(database); + } + + FreeString(my->prefix); + my->prefix = EmptyString; +} + +/////////////////////////////////////////////////////////////////////////////// + +void DefineDatabasePrefix +( + MySql_t *my, // valid struct + ccp prefix // NULL or default prefix, only for external usage +) +{ + DASSERT(my); + + FreeString(my->prefix); + my->prefix = prefix && *prefix ? STRDUP(prefix) : EmptyString; +} + +/////////////////////////////////////////////////////////////////////////////// + +static enumError CheckOpenMYSQL ( MySql_t *my, uint loglevel ) +{ + if (AutoOpenMYSQL(my,loglevel)) + return ERR_OK; + + if ( loglevel >= MYLL_ERROR ) + ERROR0(ERR_SEMANTIC,"No database connected.\n"); + return ERR_SEMANTIC; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// queries + +enumError DoQueryMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + ccp query, // query string + int query_length // length of 'query', -1 if unknown +) +{ + DASSERT(my); + DASSERT(query); + + const u64 start_time = GetTimeUSec(false); + + enumError err = CheckOpenMYSQL(my,loglevel); + if (err) + return err; + + if ( query_length < 0 ) + query_length = strlen(query); + my->total_query_count++; + my->total_query_size += query_length; + + if (mysql_real_query(my->mysql,query,query_length)) + { + TRACE("QUERY: %.*s\n",query_length,query); + my->field_count = 0; + GetStatusMYSQL(&my->status,my->mysql); + + bool fail = true; + if ( my->status.status == CR_SERVER_GONE_ERROR && my->auto_reconnect ) + { + MySqlStatus_t save; + MoveStatusMYSQL(&save,true,&my->status); + CloseMYSQL(my); + if ( OpenMYSQL(my,loglevel) || mysql_real_query(my->mysql,query,query_length) ) + MoveStatusMYSQL(&my->status,false,&save); + else + { + ClearStatusMYSQL(&save); + fail = false; + } + } + + if (fail) + { + if ( loglevel >= MYLL_QUERY_ON_ERROR ) + ERROR0(ERR_DATABASE,"%s (#%d)\n--QUERY--\n%.*s\n", + my->status.message, my->status.status, + query_length, query ); + else if ( loglevel >= MYLL_ERROR ) + ERROR0(ERR_DATABASE,"%s (#%d)\n", + my->status.message, my->status.status ); + + my->last_duration_usec = GetTimeUSec(false) - start_time; + if ( my->max_duration_usec < my->last_duration_usec ) + my->max_duration_usec = my->last_duration_usec; + my->total_duration_usec += my->last_duration_usec; + return ERR_DATABASE; + } + } + + if ( loglevel >= MYLL_QUERY_ALWAYS ) + ERROR0(ERR_OK,"%.*s\n", query_length, query ); + + my->field_count = mysql_field_count(my->mysql); + ClearStatusMYSQL(&my->status); + + my->last_duration_usec = GetTimeUSec(false) - start_time; + if ( my->max_duration_usec < my->last_duration_usec ) + my->max_duration_usec = my->last_duration_usec; + my->total_duration_usec += my->last_duration_usec; + return ERR_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError PrintArgQueryMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + ccp format, // format string for vsnprintf() + va_list arg // parameters for 'format' +) +{ + DASSERT(my); + DASSERT(format); + + char buf[10000]; + + va_list arg2; + va_copy(arg2,arg); + int stat = vsnprintf(buf,sizeof(buf),format,arg2); + va_end(arg2); + + if ( stat < sizeof(buf) ) + return DoQueryMYSQL(my,loglevel,buf,stat); + + + //--- buffer too small, use dynamic memory + + noPRINT("PrintQueryMYSQL() -> MALLOC(%u)\n",stat+1); + + char *dest = MALLOC(stat+1); + stat = vsnprintf(dest,stat+1,format,arg); + + const enumError err = DoQueryMYSQL(my,loglevel,dest,stat); + FREE(dest); + return err; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError PrintQueryMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + ccp format, // format string for vsnprintf() + ... // parameters for 'format' +) +{ + DASSERT(my); + DASSERT(format); + + va_list arg; + va_start(arg,format); + enumError err = PrintArgQueryMYSQL(my,loglevel,format,arg); + va_end(arg); + return err; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// support of multi queries + +mem_t GetFirstQuery ( ccp src, int src_len ) +{ + if (!src) + { + mem_t res = {0,0}; + return res; + } + + ccp ptr = src; + ccp end = ptr + ( src_len < 0 ? strlen(src) : src_len ); + + //--- skip all leading blanks, controls and semicolons + + while ( ptr < end && ( *ptr >= 0 && *ptr <= ' ' || *ptr == ';' ) ) + ptr++; + + if ( ptr == end ) + { + mem_t res = {ptr,0}; + return res; + } + + ccp start = ptr; + while ( ptr < end ) + { + const char ch = *ptr++; + + if ( ch == ';' ) + { + mem_t res = {start,ptr-start-1}; + return res; + } + + if ( ch == '\\' && ptr < end ) + ptr++; + else if ( ch == '\'' || ch == '\"' ) + { + while ( ptr < end ) + { + const char ch2 = *ptr++; + if ( ch2 == '\\' && ptr < end ) + ptr++; + else if ( ch2 == ch ) + break; + } + } + } + mem_t res = {start,ptr-start}; + return res; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError DoMultiQueryMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + ccp query, // query string + int query_length // length of 'query', -1 if unknown +) +{ + DASSERT(my); + DASSERT(query); + + mem_t q; + q.ptr = query; + q.len = query_length < 0 ? strlen(query) : query_length; + + for(;;) + { + mem_t q1 = GetFirstQuery(q.ptr,q.len); + if (!q1.len) + break; + const enumError err = DoQueryMYSQL(my,loglevel,q1.ptr,q1.len); + if (err) + return err; + q = BehindMem(q,q1.ptr+q1.len); + } + return ERR_OK; +} + +/////////////////////////////////////////////////////////////////////////////// + +enumError PrintMultiQueryMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + ccp format, // format string for vsnprintf() + ... // parameters for 'format' +) +{ + DASSERT(my); + DASSERT(format); + + char buf[10000]; + va_list arg; + va_start(arg,format); + int stat = vsnprintf(buf,sizeof(buf),format,arg); + va_end(arg); + + if ( stat < sizeof(buf) ) + return DoMultiQueryMYSQL(my,loglevel,buf,stat); + + + //--- buffer too small, use dynamic memory + + noPRINT("PrintQueryMYSQL() -> MALLOC(%u)\n",stat+1); + + char *dest = MALLOC(stat+1); + va_start(arg,format); + stat = vsnprintf(dest,stat+1,format,arg); + va_end(arg); + + const enumError err = DoMultiQueryMYSQL(my,loglevel,dest,stat); + FREE(dest); + return err; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// temp buffer support + +mem_t CopyMemMYSQL ( MySql_t * my, mem_t * src ) // 'src' may be NULL +{ + if ( src && src->len > 0 ) + { + if ( src->len < sizeof(my->tempbuf) && !my->tempbuf_used ) + { + noPRINT("MYSQL: use tempbuf(%u)\n",src->len); + memcpy(my->tempbuf,src->ptr,src->len); + my->tempbuf[src->len] = 0; + my->tempbuf_used = true; + + mem_t res = { my->tempbuf, src->len }; + return res; + } + + return DupMem(*src); + } + + mem_t res = {NULL,0}; + return res; +} + +/////////////////////////////////////////////////////////////////////////////// + +void FreeMemMYSQL ( MySql_t * my, mem_t mem ) +{ + DASSERT(my); + if ( mem.ptr == my->tempbuf ) + { + noPRINT("MYSQL: free tempbuf\n"); + my->tempbuf_used = false; + } + else + FREE((char*)mem.ptr); +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// fetch single value as result of a query + +mem_t FetchScalarMYSQL // FREE the result! +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel // log level for mysql errors and queries +) +{ + DASSERT(my); + mem_t ret = {NULL,0}; + + MySqlResult_t *res = UseResultMYSQL(my,loglevel); + if (res) + { + ret = CopyMemMYSQL(my,GetNextRowMYSQL(res)); + while (GetNextRowMYSQL(res)) // get all rows + ; + FreeResultMYSQL(res); + } + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// + +s64 FetchIntMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + s64 return_default // return this value on non existent result +) +{ + DASSERT(my); + mem_t res = FetchScalarMYSQL(my,loglevel); + if (!res.len) + return return_default; + + char *end; + const s64 val = strtoll(res.ptr,&end,10); + FreeMemMYSQL(my,res); + return end == res.ptr + res.len ? val : return_default; +} + +/////////////////////////////////////////////////////////////////////////////// + +u64 FetchUIntMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + u64 return_default // return this value on non existent result +) +{ + DASSERT(my); + mem_t res = FetchScalarMYSQL(my,loglevel); + if (!res.len) + return return_default; + + char *end; + const u64 val = strtoull(res.ptr,&end,10); + FreeMemMYSQL(my,res); + return end == res.ptr + res.len ? val : return_default; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// print query & fetch single value as result + +mem_t PrintArgFetchScalarMYSQL +( + // if res.len>0: FREE the result using FreeMemMYSQL(my,mem) + + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + ccp format, // format string for vsnprintf() + va_list arg // parameters for 'format' +) +{ + DASSERT(my); + DASSERT(format); + + mem_t ret = {NULL,0}; + if (!PrintArgQueryMYSQL(my,loglevel,format,arg)) + { + MySqlResult_t *res = UseResultMYSQL(my,loglevel); + if (res) + { + ret = CopyMemMYSQL(my,GetNextRowMYSQL(res)); + while (GetNextRowMYSQL(res)) // get all rows + ; + FreeResultMYSQL(res); + } + } + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// + +mem_t PrintFetchScalarMYSQL +( + // if res.len>0: FREE the result using FreeMemMYSQL(my,mem) + + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + ccp format, // format string for vsnprintf() + ... // parameters for 'format' +) +{ + DASSERT(my); + DASSERT(format); + + va_list arg; + va_start(arg,format); + mem_t res = PrintArgFetchScalarMYSQL(my,loglevel,format,arg); + va_end(arg); + return res; +} + +/////////////////////////////////////////////////////////////////////////////// + +s64 PrintFetchIntMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + s64 return_default, // return this value on non existent result + ccp format, // format string for vsnprintf() + ... // parameters for 'format' +) +{ + DASSERT(my); + DASSERT(format); + + va_list arg; + va_start(arg,format); + mem_t res = PrintArgFetchScalarMYSQL(my,loglevel,format,arg); + va_end(arg); + + if (!res.len) + return return_default; + + char *end; + const s64 val = strtoll(res.ptr,&end,10); + FreeMemMYSQL(my,res); + return end == res.ptr + res.len ? val : return_default; +} + +/////////////////////////////////////////////////////////////////////////////// + +u64 PrintFetchUIntMYSQL +( + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + u64 return_default, // return this value on non existent result + ccp format, // format string for vsnprintf() + ... // parameters for 'format' +) +{ + DASSERT(my); + DASSERT(format); + + va_list arg; + va_start(arg,format); + mem_t res = PrintArgFetchScalarMYSQL(my,loglevel,format,arg); + va_end(arg); + + if (!res.len) + return return_default; + + char *end; + const u64 val = strtoull(res.ptr,&end,10); + FreeMemMYSQL(my,res); + return end == res.ptr + res.len ? val : return_default; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// fetch single row + +void * FetchSingleRowN +( + // returns NULL on error or emtpy result, + // or a temporary buffer => call FREE(result) + // following rows will be skipped + + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + mem_t *dest, // dest buffer, unused elements are set to {0,0} + uint n_dest // number of elements in 'dest' +) +{ + DASSERT(my); + DASSERT( dest || !n_dest ); + memset(dest,0,sizeof(*dest)*n_dest); + + char *buf = 0; + MySqlResult_t *res = UseResultMYSQL(my,loglevel); + if (res) + { + mem_t *row = GetNextRowMYSQL(res); + if (row) + { + if ( n_dest > res->field_count ) + n_dest = res->field_count; + + //--- count memory usage and alloc buffer + + uint i, need = 0; + for ( i = 0; i < n_dest; i++ ) + if ( row[i].len ) + need += row[i].len + 1; + + //--- get row data + + buf = MALLOC(need); + char *ptr = buf; + for ( i = 0; i < n_dest; i++, row++, dest++ ) + { + if ( row->len ) + { + dest->ptr = ptr; + memcpy(ptr,row->ptr,row->len); + dest->len = row->len; + ptr += row->len; + *ptr++ = 0; + DASSERT( ptr <= buf+need ); + } + } + DASSERT( ptr == buf+need ); + + + //--- get and ignore all remaining rows + + while (GetNextRowMYSQL(res)) + ; + } + FreeResultMYSQL(res); + } + + return buf; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +int FetchSingleRowList +( + // returns -1 on error, 0 on empty result or the number of retrieved field + // following rows will be skipped + + MySql_t *my, // valid struct + LogLevelMYSQL loglevel, // log level for mysql errors and queries + mem_list_t *dest, // destination list + bool init_dest, // true: initialize 'dest' + bool replace_null // true: replace NULL by EmptyString +) +{ + DASSERT(my); + DASSERT(dest); + + if (init_dest) + InitializeMemList(dest); + dest->used = 0; + + int stat = -1; + MySqlResult_t *res = UseResultMYSQL(my,loglevel); + if (res) + { + stat = 0; + mem_t *row = GetNextRowMYSQL(res); + if (row) + { + stat = res->field_count; + AssignMemListN(dest,row,stat,replace_null?3:0); + + //--- get and ignore all remaining rows + while (GetNextRowMYSQL(res)) + ; + } + FreeResultMYSQL(res); + } + + return stat; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// count tables and columns + +int CountTablesMYSQL +( + MySql_t *my, // valid DB connection + ccp table, // like "table"; if NULL|EMPTY: count tables + ccp database // database name; if NULL|EMPTY: use default DB +) +{ + DASSERT(my); + + int stat = -1; + mem_t db = EscapeStringMYSQL(my, database && *database ? database : my->database ); + mem_t tab = EscapeStringMYSQL(my, table && *table ? table : "%" ); + + stat = PrintFetchIntMYSQL(my,MYLL_QUERY_ON_ERROR,-1, + "SELECT COUNT(DISTINCT TABLE_NAME) FROM information_schema.COLUMNS\n" + "WHERE TABLE_SCHEMA = \"%s\" && TABLE_NAME like \"%s\"\n", + db.ptr, tab.ptr ); + + FreeString(tab.ptr); + FreeString(db.ptr); + return stat; +} + +/////////////////////////////////////////////////////////////////////////////// + +int CountColumnsMYSQL +( + MySql_t *my, // valid DB connection + ccp column, // column name; if NULL|EMPTY: count columns + ccp table, // like "table"; if NULL|EMPTY: all tables + ccp database // database name; if NULL|EMPTY: use default DB +) +{ + DASSERT(my); + + int stat = -1; + mem_t db = EscapeStringMYSQL(my, database && *database ? database : my->database ); + mem_t tab = EscapeStringMYSQL(my, table && *table ? table : "%" ); + + if ( column && *column ) + { + mem_t col = EscapeStringMYSQL(my,column); + stat = PrintFetchIntMYSQL(my,MYLL_QUERY_ON_ERROR,-1, + "SELECT COUNT(*) FROM information_schema.COLUMNS\n" + "WHERE TABLE_SCHEMA = \"%s\" && TABLE_NAME like \"%s\"" + " && COLUMN_NAME = \"%s\"\n", + db.ptr, tab.ptr, col.ptr ); + FreeString(col.ptr); + } + else + { + stat = PrintFetchIntMYSQL(my,MYLL_QUERY_ON_ERROR,-1, + "SELECT COUNT(*) FROM information_schema.COLUMNS\n" + "WHERE TABLE_SCHEMA = \"%s\" && TABLE_NAME like \"%s\"\n", + db.ptr, tab.ptr ); + } + + FreeString(tab.ptr); + FreeString(db.ptr); + return stat; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// helpers + +mem_t EscapeMYSQL ( MySql_t *my, cvp ptr, int len ) +{ + DASSERT(my); + + if ( len < 0 ) + len = ptr ? strlen(ptr) : 0; + + mem_t res; + if ( !ptr || !len ) + { + res.ptr = EmptyString; + res.len = 0; + } + else + { + char *buf = MALLOC(2*len+1); + res.ptr = buf; + res.len = mysql_real_escape_string(my->mysql,buf,ptr,len); + } + return res; +} + +//----------------------------------------------------------------------------- + +mem_t EscapeMYSQLCirc ( MySql_t *my, cvp ptr, int len ) +{ + DASSERT(my); + + if ( len < 0 ) + len = ptr ? strlen(ptr) : 0; + + mem_t res; + if ( !ptr || !len ) + { + res.ptr = EmptyString; + res.len = 0; + } + else if ( len >= CIRC_BUF_MAX_ALLOC ) + { + // no chance + res.ptr = 0; + res.len = 0; + } + else + { + char buf[2*CIRC_BUF_MAX_ALLOC+10]; + res.len = mysql_real_escape_string(my->mysql,buf,ptr,len); + if ( res.len < CIRC_BUF_MAX_ALLOC ) + res.ptr = CopyCircBuf(buf,res.len+1); + else + { + res.ptr = 0; + res.len = 0; + } + } + return res; +} + +/////////////////////////////////////////////////////////////////////////////// + +mem_t QuoteMYSQL ( MySql_t *my, cvp ptr, int len, bool null_if_empty ) +{ + if ( len < 0 ) + len = ptr ? strlen(ptr) : 0; + + mem_t res; + if ( !ptr || !len && null_if_empty ) + { + res.ptr = STRDUP("NULL"); + res.len = 4; + } + else if (!len) + { + res.ptr = EmptyQuote; + res.len = 0; + } + else + { + char *buf = MALLOC(2*len+3); + res.ptr = buf; + res.len = mysql_real_escape_string(my->mysql,buf+1,ptr,len) + 2; + buf[0] = buf[res.len-1] = '"'; + buf[res.len] = 0; + } + return res; +} + +//----------------------------------------------------------------------------- + +mem_t QuoteMYSQLCirc ( MySql_t *my, cvp ptr, int len, bool null_if_empty ) +{ + if ( len < 0 ) + len = ptr ? strlen(ptr) : 0; + + mem_t res; + if ( !ptr || !len && null_if_empty ) + { + res.ptr = CopyCircBuf("NULL",5); + res.len = 4; + } + else if (!len) + { + res.ptr = EmptyQuote; + res.len = 2; + } + else if ( len >= CIRC_BUF_MAX_ALLOC-2 ) + { + // no chance + res.ptr = 0; + res.len = 0; + } + else + { + char buf[2*CIRC_BUF_MAX_ALLOC+10]; + res.len = mysql_real_escape_string(my->mysql,buf+1,ptr,len) + 2; + if ( res.len < CIRC_BUF_MAX_ALLOC ) + { + buf[0] = buf[res.len-1] = '"'; + buf[res.len] = 0; + res.ptr = CopyCircBuf(buf,res.len+1); + } + else + { + res.ptr = 0; + res.len = 0; + } + } + return res; +} + +/////////////////////////////////////////////////////////////////////////////// + +TransferStats_t GetStatsMYSQL ( MySql_t *my ) +{ + TransferStats_t tf; + memset(&tf,0,sizeof(tf)); + + if (my) + { + tf.conn_count = my->total_connect_count; + tf.recv_count = my->total_row_count; + tf.recv_size = my->total_field_size; + tf.send_count = my->total_query_count; + tf.send_size = my->total_query_size; + } + return tf; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// struct MySqlResult_t /////////////// +/////////////////////////////////////////////////////////////////////////////// + +static void InsertResultMYSQL ( MySqlResult_t ** first, MySqlResult_t * res ) +{ + DASSERT(first); + DASSERT(res); + DASSERT( res != *first ); // already cleared! + + //--- remove res from previous list + + if (res->prev_result) + res->prev_result->next_result = res->next_result; + if (res->next_result) + res->next_result->prev_result = res->prev_result; + + //--- insert at top of the new list + + res->prev_result = 0; + res->next_result = *first; + if (*first) + (*first)->prev_result = res; + *first = res; + + FREE(res->row); + res->row = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +MySqlResult_t * UseResultMYSQL ( MySql_t *my, uint loglevel ) +{ + DASSERT(my); + enumError err = CheckOpenMYSQL(my,loglevel); + if (err) + { + AssignStatusMYSQL(&my->status,1,"No connection to database!",1); + return NULL; + } + + MYSQL_RES *myres = mysql_use_result(my->mysql); + if (!myres) + { + GetStatusMYSQL(&my->status,my->mysql); + if ( loglevel >= MYLL_ERROR ) + ERROR0(ERR_DATABASE,"%s\n",my->status.message); + return NULL; + } + my->total_result_count++; + + MySqlResult_t *res; + if (my->pool_result) + { + res = my->pool_result; + my->pool_result = res->next_result; + } + else + res = CALLOC(1,sizeof(*res)); + InsertResultMYSQL(&my->first_result,res); + + res->mysql = my; + res->myres = myres; + res->loglevel = loglevel; + res->row_count = 0; + res->field_count = mysql_num_fields(myres); + res->row = CALLOC(sizeof(*res->row),res->field_count); + + GetStatusMYSQL(&res->status,my->mysql); + + return res; +} + +/////////////////////////////////////////////////////////////////////////////// + +void FreeResultMYSQL ( MySqlResult_t * res ) +{ + if (res) + { + MySql_t *my = res->mysql; + DASSERT(my); + + CloseResultMYSQL(res); + ClearStatusMYSQL(&res->status); + if ( my->first_result == res ) + my->first_result = res->next_result; + InsertResultMYSQL(&my->pool_result,res); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void CloseResultMYSQL ( MySqlResult_t * res ) +{ + DASSERT(res); + if (res->myres) + { + mysql_free_result(res->myres); + res->myres = 0; + } + FREE(res->row); + res->row = 0; + res->field_count = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +void FreeResultListMYSQL ( MySqlResult_t * first ) +{ + while (first) + { + MySqlResult_t *res = first; + first = res->next_result; + CloseResultMYSQL(res); + ClearStatusMYSQL(&res->status); + FREE(res); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +mem_t * GetNextRowMYSQL ( MySqlResult_t * res ) +{ + DASSERT(res); + if ( !res || !res->myres || !res->row ) + return NULL; + + MYSQL_ROW myrow = mysql_fetch_row(res->myres); + ulong *mylen = mysql_fetch_lengths(res->myres); + res->myrow = myrow; + if ( !myrow || !mylen ) + { + FREE(res->row); + res->row = 0; + return NULL; + } + res->mysql->total_row_count++; + + mem_t *dest = res->row; + uint n = res->field_count; + res->mysql->total_field_count += n; + while ( n-- > 0 ) + { + dest->ptr = *myrow++; + dest->len = *mylen++; + res->mysql->total_field_size += dest->len; + noPRINT("%3u: |%.*s|\n",dest->len,dest->len<50?dest->len:50,dest->ptr); + dest++; + } + + return res->row; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// struct MySqlStatus_t /////////////// +/////////////////////////////////////////////////////////////////////////////// + +void ClearStatusMYSQL ( MySqlStatus_t *stat ) +{ + DASSERT(stat); + if (stat->alloced) + { + FREE((char*)stat->message); + stat->alloced = false; + } + stat->message = stat->msgbuf; + stat->msgbuf[0] = 0; + stat->status = 0; +} + +/////////////////////////////////////////////////////////////////////////////// + +uint GetStatusMYSQL ( MySqlStatus_t *stat, MYSQL *my ) +{ + DASSERT(stat); + if (my) + AssignStatusMYSQL(stat,mysql_errno(my),mysql_error(my),0); + else + ClearStatusMYSQL(stat); + + return stat->status; +} + +/////////////////////////////////////////////////////////////////////////////// + +void AssignStatusMYSQL +( + MySqlStatus_t *stat, // valid struct + int stat_code, // error status to assign + ccp message, // NULL or pointer to message + int copy_mode // 0:copy message, 1:use ptr, 2:use+free ptr +) +{ + DASSERT(stat); + ClearStatusMYSQL(stat); + stat->status = stat_code; + + if (message) + { + if ( copy_mode > 0 ) + { + stat->message = message; + stat->alloced = copy_mode > 1; + } + else + { + const uint len = strlen(message); + if ( len < sizeof(stat->msgbuf) ) + { + memcpy(stat->msgbuf,message,len+1); + stat->message = stat->msgbuf; + stat->alloced = false; + } + else + { + stat->message = MEMDUP(message,len); + stat->alloced = true; + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void CopyStatusMYSQL +( + MySqlStatus_t *dest, // NULL or initialized status + bool init_dest, // true: initialize 'dest' + const MySqlStatus_t *src // NULL (clear dest) + // or initialized status (assign to dest) +) +{ + if (dest) + { + if (init_dest) + InitializeStatusMYSQL(dest); + + if ( dest != src ) + { + if (src) + AssignStatusMYSQL(dest,src->status,src->message,0); + else + ClearStatusMYSQL(dest); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void MoveStatusMYSQL +( + MySqlStatus_t *dest, // NULL or initialized status + bool init_dest, // true: initialize 'dest' + MySqlStatus_t *src // NULL (clear dest) + // or initialized status (move to dest) +) +{ + if ( dest != src ) + { + if (dest) + { + if (init_dest) + InitializeStatusMYSQL(dest); + else + ClearStatusMYSQL(dest); + + if (src) + { + memcpy(dest,src,sizeof(*dest)); + if ( src->message == src->msgbuf ) + dest->message = dest->msgbuf; + memset(src,0,sizeof(*src)); + } + } + else if (src) + ClearStatusMYSQL(src); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void PrintStatusMYSQL +( + MySqlStatus_t *stat, // valid struct + int stat_code, // error status to assign + ccp format, // format string for vsnprintf() + ... // parameters for 'format' +) +{ + DASSERT(stat); + DASSERT(format); + + char buf[10000]; + va_list arg; + va_start(arg,format); + int len = vsnprintf(buf,sizeof(buf),format,arg); + va_end(arg); + + if ( len < sizeof(buf) ) + AssignStatusMYSQL(stat,stat_code,buf,0); + else + { + //--- buffer too small, use dynamic memory + + noPRINT("PrintQueryMYSQL() -> MALLOC(%u)\n",stat+1); + + char *dest = MALLOC(len+1); + va_start(arg,format); + len = vsnprintf(dest,len+1,format,arg); + va_end(arg); + + AssignStatusMYSQL(stat,stat_code,dest,2); + } +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// MySqlServerStats_t /////////////// +/////////////////////////////////////////////////////////////////////////////// + +enumError GetMySqlServerStats ( MySql_t *my, MySqlServerStats_t *stat ) +{ + DASSERT(my); + DASSERT(stat); + InitializeMySqlServerStats(stat); + + static const char query[] + = "show status where variable_name in" + " ('Uptime','Max_used_connections','Connections','Queries')"; + + enumError err + = DoQueryMYSQL(my,MYLL_QUERY_ON_ERROR,query,sizeof(query)-1); + if (!err) + { + MySqlResult_t *res = UseResultMYSQL(my,false); + if (res) + { + for(;;) + { + mem_t *row = GetNextRowMYSQL(res); + if (!row) + break; + + //printf("|%s|%s|\n",row[0].ptr,row[1].ptr); + switch(row[0].ptr[0]) + { + case 'C': + if (!strcmp(row[0].ptr,"Connections")) + stat->connections = strtoull(row[1].ptr,0,10); + break; + + case 'M': + if (!strcmp(row[0].ptr,"Max_used_connections")) + stat->max_connections = strtoull(row[1].ptr,0,10); + break; + + case 'Q': + if (!strcmp(row[0].ptr,"Queries")) + stat->queries = strtoull(row[1].ptr,0,10); + break; + + case 'U': + if (!strcmp(row[0].ptr,"Uptime")) + stat->uptime = strtoull(row[1].ptr,0,10); + break; + } + } + FreeResultMYSQL(res); + } + } + return err; +} + +/////////////////////////////////////////////////////////////////////////////// + +MySqlServerStats_t * Add3MySqlServerStats +( + // return dest + // calculate: dest = src1 + src2 + + MySqlServerStats_t *dest, // NULL or destination (maybe same as source) + const MySqlServerStats_t *src1, // NULL or first source + const MySqlServerStats_t *src2 // NULL or second source +) +{ + if (dest) + { + if (!src1) + { + if (src2) + memcpy(dest,src2,sizeof(*dest)); + else + InitializeMySqlServerStats(dest); + } + else if (!src2) + { + DASSERT(src1); + memcpy(dest,src1,sizeof(*dest)); + } + else + { + DASSERT(src1); + DASSERT(src2); + + dest->max_connections = src1->max_connections > src2->max_connections + ? src1->max_connections : src2->max_connections; + dest->uptime = src1->uptime + src2->uptime; + dest->connections = src1->connections + src2->connections; + dest->queries = src1->queries + src2->queries; + } + } + return dest; +} + +/////////////////////////////////////////////////////////////////////////////// + +MySqlServerStats_t * Sub3MySqlServerStats +( + // return dest + // calculate: dest = src1 - src2 + + MySqlServerStats_t *dest, // NULL or destination (maybe same as source) + const MySqlServerStats_t *src1, // NULL or first source + const MySqlServerStats_t *src2 // NULL or second source +) +{ + if (dest) + { + if (!src1) + { + if (src2) + { + MySqlServerStats_t temp; + InitializeMySqlServerStats(&temp); + Sub3MySqlServerStats(dest,&temp,src2); + } + else + InitializeMySqlServerStats(dest); + } + else if (!src2) + { + DASSERT(src1); + memcpy(dest,src1,sizeof(*dest)); + } + else + { + DASSERT(src1); + DASSERT(src2); + dest->max_connections = src1->max_connections; + dest->uptime = src1->uptime - src2->uptime; + dest->connections = src1->connections - src2->connections; + dest->queries = src1->queries - src2->queries; + } + } + return dest; +} + +/////////////////////////////////////////////////////////////////////////////// + +MySqlServerStats_t * Max2MySqlServerStats +( + // return dest + // calculate: dest := max(dest,src) + + MySqlServerStats_t *dest, // NULL or destination (maybe same as source) + const MySqlServerStats_t *src // NULL or source +) +{ + if ( src && dest ) + { + if ( dest->max_connections < src->max_connections ) + dest->max_connections = src->max_connections; + + if ( dest->uptime < src->uptime ) + dest->uptime = src->uptime; + + if ( dest->connections < src->connections ) + dest->connections = src->connections; + + if ( dest->queries < src->queries ) + dest->queries = src->queries; + } + return dest; +} + +// +/////////////////////////////////////////////////////////////////////////////// +/////////////// END /////////////// +/////////////////////////////////////////////////////////////////////////////// |
