From cc4f8982da5504eaf8804d5b7a9f74b0c6e43a35 Mon Sep 17 00:00:00 2001 From: TimeServ Date: Sat, 18 Dec 2010 21:30:16 +0000 Subject: [PATCH] msvc2010 fix rename win_mysql -> sv_sql make mysql calls os independent git-svn-id: https://svn.code.sf.net/p/fteqw/code/branches/wip@3702 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/common/bothdefs.h | 1 + engine/common/netinc.h | 27 +- engine/server/pr_cmds.c | 850 ++------------------------------------ engine/server/sv_sql.c | 813 ++++++++++++++++++++++++++++++++++++ engine/server/sv_sql.h | 91 ++++ engine/server/win_mysql.c | 98 ----- engine/server/win_mysql.h | 130 ------ 7 files changed, 958 insertions(+), 1052 deletions(-) create mode 100644 engine/server/sv_sql.c create mode 100644 engine/server/sv_sql.h delete mode 100644 engine/server/win_mysql.c delete mode 100644 engine/server/win_mysql.h diff --git a/engine/common/bothdefs.h b/engine/common/bothdefs.h index ae1f6496a..0b0b157a0 100644 --- a/engine/common/bothdefs.h +++ b/engine/common/bothdefs.h @@ -265,6 +265,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #undef HLSERVER #undef WEBSERVER #undef VM_Q1 + #undef SQL #endif //remove any options that depend upon GL. diff --git a/engine/common/netinc.h b/engine/common/netinc.h index ed02fc5d9..fb1b39c25 100644 --- a/engine/common/netinc.h +++ b/engine/common/netinc.h @@ -1,14 +1,6 @@ #ifdef _WIN32 - #define EWOULDBLOCK WSAEWOULDBLOCK - #define EMSGSIZE WSAEMSGSIZE - #define ECONNRESET WSAECONNRESET - #define ECONNABORTED WSAECONNABORTED - #define ECONNREFUSED WSAECONNREFUSED - #define EADDRNOTAVAIL WSAEADDRNOTAVAIL - #define EAFNOSUPPORT WSAEAFNOSUPPORT - #ifdef _MSC_VER #define USEIPX #endif @@ -17,6 +9,7 @@ #include "wsipx.h" #endif #include + #include #ifndef IPPROTO_IPV6 /*for msvc6*/ #define IPPROTO_IPV6 @@ -69,6 +62,24 @@ #endif #endif + #if (_MSC_VER >= 1600) + #undef EADDRNOTAVAIL + #undef EAFNOSUPPORT + #undef ECONNABORTED + #undef ECONNREFUSED + #undef ECONNRESET + #undef EMSGSIZE + #undef EWOULDBLOCK + #endif + + #define EWOULDBLOCK WSAEWOULDBLOCK + #define EMSGSIZE WSAEMSGSIZE + #define ECONNRESET WSAECONNRESET + #define ECONNABORTED WSAECONNABORTED + #define ECONNREFUSED WSAECONNREFUSED + #define EADDRNOTAVAIL WSAEADDRNOTAVAIL + #define EAFNOSUPPORT WSAEAFNOSUPPORT + #else #include #include diff --git a/engine/server/pr_cmds.c b/engine/server/pr_cmds.c index 8aadbb02d..1a31821d8 100644 --- a/engine/server/pr_cmds.c +++ b/engine/server/pr_cmds.c @@ -21,7 +21,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "qwsvdef.h" #ifdef SQL -#include "win_mysql.h" +#include "sv_sql.h" #endif #define G_PROG G_FLOAT @@ -80,16 +80,6 @@ extern cvar_t sv_gameplayfix_noairborncorpse; cvar_t sv_addon[MAXADDONS]; char cvargroup_progs[] = "Progs variables"; -#ifdef SQL -cvar_t sql_driver = SCVARF("sv_sql_driver", "mysql", CVAR_NOUNSAFEEXPAND); -cvar_t sql_host = SCVARF("sv_sql_host", "127.0.0.1", CVAR_NOUNSAFEEXPAND); -cvar_t sql_username = SCVARF("sv_sql_username", "", CVAR_NOUNSAFEEXPAND); -cvar_t sql_password = SCVARF("sv_sql_password", "", CVAR_NOUNSAFEEXPAND); -cvar_t sql_defaultdb = SCVARF("sv_sql_defaultdb", "", CVAR_NOUNSAFEEXPAND); - -#define SQLCVAROPTIONS "SQL Defaults" -#endif - evalc_t evalc_idealpitch, evalc_pitch_speed; int pr_teamfield; @@ -99,13 +89,6 @@ static void PRSV_ClearThreads(void); void PR_fclose_progs(progfuncs_t*); void PF_InitTempStrings(progfuncs_t *prinst); -#ifdef SQL -// SQL prototypes -void SQL_Init(void); -void SQL_KillServers(void); -void SQL_DeInit(void); -#endif - typedef struct { //for func finding and swapping. char *name; @@ -5109,302 +5092,15 @@ static void QCBUILTIN PF_chat (progfuncs_t *prinst, struct globalvars_s *pr_glob - - - - - - - - - - - - -// FTE SQL functions +/* FTE_SQL builtins */ #ifdef SQL -#define SQL_CONNECT_STRUCTPARAMS 2 -#define SQL_CONNECT_PARAMS 4 - -typedef enum -{ - SQLDRV_MYSQL, -// SQLDRV_SQLITE, NOT IN YET - SQLDRV_INVALID -} sqldrv_t; - -typedef struct queryrequest_s -{ - int num; // query number reference - qboolean persistant; // persistant query - struct queryrequest_s *next; // next request in queue - int callback; // callback function reference - int selfent; // self entity on call - float selfid; // self entity id on call - int otherent; // other entity on call - float otherid; // other entity id on call - char query[1]; // query to run (struct hack) -} queryrequest_t; - -typedef struct queryresult_s -{ - struct queryrequest_s *request; // corresponding request - struct queryresult_s *next; // next result in queue - int rows; // rows contained in single result set - int columns; // fields - qboolean eof; // end of query reached - MYSQL_RES *result; // result set from mysql -// char **resultset; // stored result set from partial fetch - char error[1]; // error string, "" if none (struct hack) -} queryresult_t; - -typedef struct sqlserver_s -{ - void *thread; // worker thread for server - MYSQL *mysql; // mysql server - volatile qboolean active; // set to false to kill thread - void *requestcondv; // lock and conditional variable for queue read/write - void *resultlock; // mutex for queue read/write - int querynum; // next reference number for queries - queryrequest_t *requests; // query requests queue - queryrequest_t *requestslast; // query requests queue last link - queryresult_t *results; // query results queue - queryresult_t *resultslast; // query results queue last link - queryresult_t *currentresult; // current called result - queryresult_t *persistresults; // list of persistant results - queryresult_t *serverresult; // server error results - char **connectparams; // connect parameters (0 = host, 1 = user, 2 = pass, 3 = defaultdb) -} sqlserver_t; - -void SQL_PushResult(sqlserver_t *server, queryresult_t *qres) -{ - Sys_LockMutex(server->resultlock); - qres->next = NULL; - if (!server->resultslast) - server->results = server->resultslast = qres; - else - server->resultslast = server->resultslast->next = qres; - Sys_UnlockMutex(server->resultlock); -} - -queryresult_t *SQL_PullResult(sqlserver_t *server) -{ - queryresult_t *qres; - Sys_LockMutex(server->resultlock); - qres = server->results; - if (qres) - { - server->results = qres->next; - if (!server->results) - server->resultslast = NULL; - } - Sys_UnlockMutex(server->resultlock); - - return qres; -} - -void SQL_PushRequest(sqlserver_t *server, queryrequest_t *qreq) -{ - Sys_LockConditional(server->requestcondv); - qreq->next = NULL; - if (!server->requestslast) - server->requests = server->requestslast = qreq; - else - server->requestslast = server->requestslast->next = qreq; - Sys_UnlockConditional(server->requestcondv); -} - -queryrequest_t *SQL_PullRequest(sqlserver_t *server, qboolean lock) -{ - queryrequest_t *qreq; - if (lock) - Sys_LockConditional(server->requestcondv); - qreq = server->requests; - if (qreq) - { - server->requests = qreq->next; - if (!server->requests) - server->requestslast = NULL; - } - Sys_UnlockConditional(server->requestcondv); - - return qreq; -} - -sqlserver_t **sqlservers; -int sqlservercount; -qboolean sqlavailable; - -int sql_serverworker(void *sref) -{ - sqlserver_t *server = (sqlserver_t *)sref; - char *error = NULL; - my_bool reconnect = 1; - int tinit, i; - qboolean needlock = false; - - if (tinit = mysql_thread_init()) - error = "MYSQL thread init failed"; - else if (!(server->mysql = mysql_init(NULL))) - error = "MYSQL init failed"; - else if (mysql_options(server->mysql, MYSQL_OPT_RECONNECT, &reconnect)) - error = "MYSQL reconnect options set failed"; - else - { - int port = 0; - char *colon; - - colon = strchr(server->connectparams[0], ':'); - if (colon) - { - *colon = '\0'; - port = atoi(colon + 1); - } - - if (!(server->mysql = mysql_real_connect(server->mysql, server->connectparams[0], server->connectparams[1], server->connectparams[2], server->connectparams[3], port, 0, 0))) - error = "MYSQL initial connect attempt failed"; - - if (colon) - *colon = ':'; - } - - for (i = SQL_CONNECT_STRUCTPARAMS; i < SQL_CONNECT_PARAMS; i++) - Z_Free(server->connectparams[i]); - - BZ_Realloc(server->connectparams, sizeof(char *) * SQL_CONNECT_STRUCTPARAMS); - - if (error) - server->active = false; - - while (server->active) - { - Sys_LockConditional(server->requestcondv); - if (!server->requests) // this is needed for thread startup and to catch any "lost" changes - Sys_ConditionWait(server->requestcondv); - needlock = false; // so we don't try to relock first round - - while (1) - { - queryrequest_t *qreq = NULL; - queryresult_t *qres; - const char *qerror = NULL; - MYSQL_RES *mysqlres = NULL; - int rows = -1; - int columns = -1; - int qesize = 0; - - if (!(qreq = SQL_PullRequest(server, needlock))) - break; - - // pullrequest makes sure our condition is unlocked but we'll need - // a lock next round - needlock = true; - - // perform the query and fill out the result structure - if (mysql_query(server->mysql, qreq->query)) - qerror = mysql_error(server->mysql); - else // query succeeded - { - mysqlres = mysql_store_result(server->mysql); - if (mysqlres) // result set returned - { - rows = mysql_num_rows(mysqlres); - columns = mysql_num_fields(mysqlres); - } - else if (mysql_field_count(server->mysql) == 0) // no result set - { - rows = mysql_affected_rows(server->mysql); - if (rows < 0) - rows = 0; - columns = 0; - } - else // error - qerror = mysql_error(server->mysql); - } - - if (qerror) - qesize = Q_strlen(qerror); - qres = (queryresult_t *)ZF_Malloc(sizeof(queryresult_t) + qesize); - if (qres) - { - if (qerror) - Q_strncpy(qres->error, qerror, qesize); - qres->result = mysqlres; - qres->rows = rows; - qres->columns = columns; - qres->request = qreq; - qres->eof = true; // store result has no more rows to read afterwards - qreq->next = NULL; - - SQL_PushResult(server, qres); - } - else // we're screwed here so bomb out - { - server->active = false; - error = "MALLOC ERROR! Unable to allocate query result!"; - break; - } - } - } - - if (server->mysql) - mysql_close(server->mysql); - - // if we have a server error we still need to put it on the queue - if (error) - { - int esize = Q_strlen(error); - queryresult_t *qres = (queryresult_t *)Z_Malloc(sizeof(queryresult_t) + esize); - if (qres) - { // hopefully the mysql_close gained us some memory otherwise we're pretty screwed - qres->rows = qres->columns = -1; - Q_strncpy(qres->error, error, esize); - - SQL_PushResult(server, qres); - } - } - - if (!tinit) - mysql_thread_end(); - - return 0; -} - -sqlserver_t *SQL_GetServer (int serveridx, qboolean inactives) -{ - if (serveridx < 0 || serveridx >= sqlservercount) - return NULL; - if (!inactives && sqlservers[serveridx]->active == false) - return NULL; - return sqlservers[serveridx]; -} - -queryresult_t *SQL_GetQueryResult (sqlserver_t *server, int queryidx) -{ - queryresult_t *qres; - - qres = server->currentresult; - if (qres && qres->request && qres->request->num == queryidx) - return qres; - - for (qres = server->persistresults; qres; qres = qres->next) - if (qres->request && qres->request->num == queryidx) - return qres; - - return NULL; -} - void PF_sqlconnect (progfuncs_t *prinst, struct globalvars_s *pr_globals) { - int serverref; char *paramstr[SQL_CONNECT_PARAMS]; - int paramsize[SQL_CONNECT_PARAMS]; - sqlserver_t *server; - int i, tsize; char *driver; - int drvchoice; + int i; - if (!sqlavailable) + if (!SQL_Available()) { G_FLOAT(OFS_RETURN) = -1; return; @@ -5428,9 +5124,6 @@ void PF_sqlconnect (progfuncs_t *prinst, struct globalvars_s *pr_globals) if (!paramstr[3][0]) paramstr[3] = sql_defaultdb.string; - for (i = 0; i < SQL_CONNECT_PARAMS; i++) - paramsize[i] = Q_strlen(paramstr[i]); - // verify/switch driver choice if (*svprogfuncs->callargc > (SQL_CONNECT_PARAMS + 1)) driver = PR_GetStringOfs(prinst, OFS_PARM0 + SQL_CONNECT_PARAMS * 3); @@ -5439,97 +5132,20 @@ void PF_sqlconnect (progfuncs_t *prinst, struct globalvars_s *pr_globals) if (!driver[0]) driver = sql_driver.string; - - if (Q_strcasecmp(driver, "mysql") == 0) - drvchoice = SQLDRV_MYSQL; - else // invalid driver choice so we bomb out - { - G_FLOAT(OFS_RETURN) = -1; - return; - } - - // alloc or realloc sql servers array - if (sqlservers == NULL) - { - serverref = 0; - sqlservercount = 1; - sqlservers = (sqlserver_t **)BZ_Malloc(sizeof(sqlserver_t *)); - } - else - { - serverref = sqlservercount; - sqlservercount++; - sqlservers = (sqlserver_t **)BZ_Realloc(sqlservers, sizeof(sqlserver_t *) * sqlservercount); - } - - // assemble server structure - tsize = 0; - for (i = 0; i < SQL_CONNECT_STRUCTPARAMS; i++) - tsize += paramsize[i] + 1; // allocate extra space for host and user only - - server = (sqlserver_t *)Z_Malloc(sizeof(sqlserver_t) + tsize); - server->connectparams = BZ_Malloc(sizeof(char *) * SQL_CONNECT_PARAMS); - - tsize = 0; - for (i = 0; i < SQL_CONNECT_STRUCTPARAMS; i++) - { - server->connectparams[i] = ((char *)(server + 1)) + tsize; - Q_strncpy(server->connectparams[i], paramstr[i], paramsize[i]); - // string should be null-terminated due to Z_Malloc - tsize += paramsize[i] + 1; - } - for (i = SQL_CONNECT_STRUCTPARAMS; i < SQL_CONNECT_PARAMS; i++) - { - server->connectparams[i] = Z_Malloc(sizeof(char) * (paramsize[i] + 1)); - Q_strncpy(server->connectparams[i], paramstr[i], paramsize[i]); - // string should be null-terminated due to Z_Malloc - } - - sqlservers[serverref] = server; - - server->querynum = 1; - server->active = true; - server->requestcondv = Sys_CreateConditional(); - server->resultlock = Sys_CreateMutex(); - - if (!server->requestcondv || !server->resultlock) - { - if (server->requestcondv) - Sys_DestroyConditional(server->requestcondv); - if (server->resultlock) - Sys_DestroyMutex(server->resultlock); - Z_Free(server); - sqlservercount--; - G_FLOAT(OFS_RETURN) = -1; - return; - } - - server->thread = Sys_CreateThread(sql_serverworker, (void *)server, 1024); - if (!server->thread) - { - Z_Free(server); - sqlservercount--; - G_FLOAT(OFS_RETURN) = -1; - return; - } - - G_FLOAT(OFS_RETURN) = serverref; + G_FLOAT(OFS_RETURN) = SQL_NewServer(driver, paramstr); } void PF_sqldisconnect (progfuncs_t *prinst, struct globalvars_s *pr_globals) { sqlserver_t *server; - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), false); if (server) { - server->active = false; - - // force the threads to reiterate requests and hopefully terminate - Sys_ConditionBroadcast(server->requestcondv); + SQL_Disconnect(server); return; } } @@ -5537,145 +5153,44 @@ void PF_sqldisconnect (progfuncs_t *prinst, struct globalvars_s *pr_globals) void PF_sqlopenquery (progfuncs_t *prinst, struct globalvars_s *pr_globals) { + sqlserver_t *server; int callfunc = G_INT(OFS_PARM1); int querytype = G_FLOAT(OFS_PARM2); char *querystr = PF_VarString(prinst, 3, pr_globals); - int qsize = Q_strlen(querystr); - queryrequest_t *qreq; - sqlserver_t *server; - int querynum; + int qself, qother; + float qselfid, qotherid; - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), false); if (server) { - qreq = (queryrequest_t *)ZF_Malloc(sizeof(queryrequest_t) + qsize); - if (qreq) - { - qreq->persistant = (querytype == 1); - qreq->callback = callfunc; + // save self and other references + if (PROG_TO_EDICT(prinst, pr_global_struct->self)->isfree) + qself = pr_global_struct->world; + else + qself = pr_global_struct->self; + qselfid = PROG_TO_EDICT(prinst, qself)->xv->uniquespawnid; + if (PROG_TO_EDICT(prinst, pr_global_struct->other)->isfree) + qother = pr_global_struct->world; + else + qother = pr_global_struct->other; + qotherid = PROG_TO_EDICT(prinst, qother)->xv->uniquespawnid; - // save self and other references - if (PROG_TO_EDICT(prinst, pr_global_struct->self)->isfree) - qreq->selfent = pr_global_struct->world; - else - qreq->selfent = pr_global_struct->self; - qreq->selfid = PROG_TO_EDICT(prinst, qreq->selfent)->xv->uniquespawnid; - if (PROG_TO_EDICT(prinst, pr_global_struct->other)->isfree) - qreq->otherent = pr_global_struct->world; - else - qreq->otherent = pr_global_struct->other; - qreq->otherid = PROG_TO_EDICT(prinst, qreq->otherent)->xv->uniquespawnid; - - querynum = qreq->num = server->querynum; - // prevent the reference num from getting too big to prevent FP problems - if (++server->querynum > 1000000) - server->querynum = 1; - - Q_strncpy(qreq->query, querystr, qsize); - - SQL_PushRequest(server, qreq); - Sys_ConditionSignal(server->requestcondv); - - G_FLOAT(OFS_RETURN) = querynum; - return; - } + G_FLOAT(OFS_RETURN) = SQL_NewQuery(server, callfunc, querytype, qself, qselfid, qother, qotherid, querystr); + return; } } // else we failed so return the error G_FLOAT(OFS_RETURN) = -1; } -void SQL_DeallocResult(queryresult_t *qres) -{ - // deallocate current result - if (qres->result) - mysql_free_result(qres->result); - if (qres->request) - Z_Free(qres->request); - - Z_Free(qres); -} - -void SQL_ClosePersistantResult(sqlserver_t *server, queryresult_t *qres) -{ - queryresult_t *prev, *cur; - - prev = server->persistresults; - if (prev == qres) - { - server->persistresults = prev->next; - SQL_DeallocResult(prev); - return; - } - - for (cur = prev->next; cur; prev = cur, cur = prev->next) - { - if (cur == qres) - { - prev = cur->next; - SQL_DeallocResult(cur); - return; - } - } -} - -void SQL_CloseResult(sqlserver_t *server, queryresult_t *qres) -{ - if (!qres) - return; - if (qres == server->currentresult) - { - SQL_DeallocResult(server->currentresult); - server->currentresult = NULL; - return; - } - // else we have a persistant query - SQL_ClosePersistantResult(server, qres); -} - -void SQL_CloseAllResults(sqlserver_t *server) -{ - queryresult_t *oldqres, *qres; - - // close orphaned results (we assume the lock is active or non-existant at this point) - qres = server->results; - while (qres) - { - oldqres = qres; - qres = qres->next; - SQL_DeallocResult(oldqres); - } - // close current - if (server->currentresult) - { - SQL_DeallocResult(server->currentresult); - server->currentresult = NULL; - } - // close persistant results - qres = server->persistresults; - while (qres) - { - oldqres = qres; - qres = qres->next; - SQL_DeallocResult(oldqres); - } - server->persistresults = NULL; - // close server result - if (server->serverresult) - { - SQL_DeallocResult(server->serverresult); - server->serverresult = NULL; - } -} - void PF_sqlclosequery (progfuncs_t *prinst, struct globalvars_s *pr_globals) { sqlserver_t *server; queryresult_t *qres; - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), false); if (server) @@ -5692,52 +5207,13 @@ void PF_sqlclosequery (progfuncs_t *prinst, struct globalvars_s *pr_globals) // else nothing to close } -char *SQL_ReadField (sqlserver_t *server, queryresult_t *qres, int row, int col, qboolean fields) -{ - if (!qres->result) // TODO: partial resultset logic not implemented yet - return NULL; - else - { // store_result query - if (qres->rows < row || qres->columns < col || col < 0) - return NULL; - - if (row < 0) - { // fetch field name - if (fields) // but only if we asked for them - { - MYSQL_FIELD *field; - - field = mysql_fetch_field_direct(qres->result, col); - - if (!field) - return NULL; - else - return field->name; - } - else - return NULL; - } - else - { // fetch data - MYSQL_ROW sqlrow; - - mysql_data_seek(qres->result, row); - sqlrow = mysql_fetch_row(qres->result); - if (!sqlrow || !sqlrow[col]) - return NULL; - else - return sqlrow[col]; - } - } -} - void PF_sqlreadfield (progfuncs_t *prinst, struct globalvars_s *pr_globals) { sqlserver_t *server; queryresult_t *qres; char *data; - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), false); if (server) @@ -5764,7 +5240,7 @@ void PF_sqlreadfloat (progfuncs_t *prinst, struct globalvars_s *pr_globals) queryresult_t *qres; char *data; - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), false); if (server) @@ -5785,13 +5261,12 @@ void PF_sqlreadfloat (progfuncs_t *prinst, struct globalvars_s *pr_globals) G_FLOAT(OFS_RETURN) = 0; } - void PF_sqlerror (progfuncs_t *prinst, struct globalvars_s *pr_globals) { sqlserver_t *server; int serverref = G_FLOAT(OFS_PARM0); - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), true); if (server) @@ -5825,7 +5300,7 @@ void PF_sqlescape (progfuncs_t *prinst, struct globalvars_s *pr_globals) char *toescape; char escaped[4096]; - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), false); if (server) @@ -5833,8 +5308,7 @@ void PF_sqlescape (progfuncs_t *prinst, struct globalvars_s *pr_globals) toescape = PR_GetStringOfs(prinst, OFS_PARM1); if (toescape) { - mysql_real_escape_string(server->mysql, escaped, toescape, strlen(toescape)); - + SQL_Escape(server, toescape, escaped, sizeof(escaped)); RETURN_TSTRING(escaped); return; } @@ -5848,12 +5322,12 @@ void PF_sqlversion (progfuncs_t *prinst, struct globalvars_s *pr_globals) { sqlserver_t *server; - if (sqlavailable) + if (SQL_Available()) { server = SQL_GetServer(G_FLOAT(OFS_PARM0), false); if (server) { - RETURN_TSTRING(va("mysql: %s", mysql_get_client_info())); + RETURN_TSTRING(SQL_Info(server)); return; } } @@ -5861,266 +5335,16 @@ void PF_sqlversion (progfuncs_t *prinst, struct globalvars_s *pr_globals) RETURN_TSTRING(""); } -// SQL related commands -void SQL_Status_f(void) -{ - int i; - - Con_Printf("%i connections\n", sqlservercount); - for (i = 0; i < sqlservercount; i++) - { - int reqnum = 0; - int resnum = 0; - queryrequest_t *qreq; - queryresult_t *qres; - - sqlserver_t *server = sqlservers[i]; - - Sys_LockMutex(server->resultlock); - Sys_LockConditional(server->requestcondv); - for (qreq = server->requests; qreq; qreq = qreq->next) - reqnum++; - for (qres = server->results; qres; qres = qres->next) - resnum++; - - Con_Printf("#%i %s@%s: %s\n", - i, - server->connectparams[1], - server->connectparams[0], - server->active ? "active" : "inactive"); - - if (reqnum) - { - Con_Printf ("- %i requests\n"); - for (qreq = server->requests; qreq; qreq = qreq->next) - { - Con_Printf (" query #%i: %s\n", - qreq->num, - qreq->query); - // TODO: function lookup? - } - } - - if (resnum) - { - Con_Printf ("- %i results\n"); - for (qres = server->results; qres; qres = qres->next) - { - Con_Printf (" * %i rows, %i columns", - qres->rows, - qres->columns); - if (qres->error[0]) - Con_Printf(", error %s\n", qres->error); - else - Con_Printf("\n"); - // TODO: request info? - } - } - - if (server->serverresult) - Con_Printf ("server result: error %s\n", server->serverresult->error); - - // TODO: list all requests, results here - Sys_UnlockMutex(server->resultlock); - Sys_UnlockConditional(server->requestcondv); - } -} - -void SQL_Kill_f (void) -{ - sqlserver_t *server; - - if (Cmd_Argc() < 2) - { - Con_Printf ("Syntax: %s serverid\n", Cmd_Argv(0)); - return; - } - - server = SQL_GetServer(atoi(Cmd_Argv(1)), false); - if (server) - { - server->active = false; - Sys_ConditionBroadcast(server->requestcondv); - return; - } -} - -void SQL_Killall_f (void) -{ - SQL_KillServers(); -} - -// SQL cycle logic -void SQL_Cycle (progfuncs_t *prinst, struct globalvars_s *pr_globals) -{ - int i; - - for (i = 0; i < sqlservercount; i++) - { - sqlserver_t *server = sqlservers[i]; - queryresult_t *qres; - - while (qres = SQL_PullResult(server)) - { - qres->next = NULL; - if (qres->request && qres->request->callback) - { - if (server->active) - { // only process results to callback if server is active - edict_t *ent; - - server->currentresult = qres; - G_FLOAT(OFS_PARM0) = i; - G_FLOAT(OFS_PARM1) = qres->request->num; - G_FLOAT(OFS_PARM2) = qres->rows; - G_FLOAT(OFS_PARM3) = qres->columns; - G_FLOAT(OFS_PARM4) = qres->eof; - - // recall self and other references - ent = PROG_TO_EDICT(prinst, qres->request->selfent); - if (ent->isfree || ent->xv->uniquespawnid != qres->request->selfid) - pr_global_struct->self = pr_global_struct->world; - else - pr_global_struct->self = qres->request->selfent; - ent = PROG_TO_EDICT(prinst, qres->request->otherent); - if (ent->isfree || ent->xv->uniquespawnid != qres->request->otherid) - pr_global_struct->other = pr_global_struct->world; - else - pr_global_struct->other = qres->request->otherent; - - PR_ExecuteProgram(prinst, qres->request->callback); - - if (qres->eof) - { - if (server->currentresult) - { - if (server->currentresult->request && server->currentresult->request->persistant) - { - // move into persistant list - server->currentresult->next = server->persistresults; - server->persistresults = server->currentresult; - } - else // just close the query - SQL_CloseResult(server, server->currentresult); - } - } - // TODO: else we move a request back into the queue? - } - } - else // error or server-only result - { - if (server->serverresult) - Z_Free(server->serverresult); - server->serverresult = qres; - } - } - server->currentresult = NULL; - } -} - void PR_SQLCycle(void) { globalvars_t *pr_globals; - if (!sqlavailable || !svprogfuncs) + if (!SQL_Available() || !svprogfuncs) return; pr_globals = PR_globals(svprogfuncs, PR_CURRENT); - SQL_Cycle(svprogfuncs, pr_globals); -} - -void SQL_MYSQLInit(void) -{ -#ifdef WIN32 - if (!mysql_dll_init()) - { - Con_Printf("mysqlclient.dll didn't load\n"); - return; - } -#endif - - if (mysql_thread_safe()) - { - if (!mysql_library_init(0, NULL, NULL)) - { - Con_Printf("MYSQL backend loaded\n"); - sqlavailable = true; - return; - } - else - Con_Printf("MYSQL library init failed!\n"); - } - else - Con_Printf("MYSQL client is not thread safe!\n"); - - mysql_dll_close(); - sqlavailable = false; -} - -void SQL_Init(void) -{ - Cmd_AddCommand ("sqlstatus", SQL_Status_f); - Cmd_AddCommand ("sqlkill", SQL_Kill_f); - Cmd_AddCommand ("sqlkillall", SQL_Killall_f); - - Cvar_Register(&sql_driver, SQLCVAROPTIONS); - Cvar_Register(&sql_host, SQLCVAROPTIONS); - Cvar_Register(&sql_username, SQLCVAROPTIONS); - Cvar_Register(&sql_password, SQLCVAROPTIONS); - Cvar_Register(&sql_defaultdb, SQLCVAROPTIONS); - - SQL_MYSQLInit(); -} - -void SQL_KillServers(void) -{ - int i; - for (i = 0; i < sqlservercount; i++) - { - sqlserver_t *server = sqlservers[i]; - queryrequest_t *qreq, *oldqreq; - - server->active = false; // set thread to kill itself - Sys_ConditionBroadcast(server->requestcondv); // force condition check - Sys_WaitOnThread(server->thread); // wait on thread to die - - // server resource deallocation (TODO: should this be done in the thread itself?) - Sys_DestroyConditional(server->requestcondv); - Sys_DestroyMutex(server->resultlock); - - // close orphaned requests - qreq = server->requests; - while (qreq) - { - oldqreq = qreq; - qreq = qreq->next; - Z_Free(oldqreq); - } - - SQL_CloseAllResults(server); - - // the alloc'ed connect params should get deallocated by the thread - if (server->connectparams) - BZ_Free(server->connectparams); - - Z_Free(server); - } - if (sqlservers) - Z_Free(sqlservers); - sqlservers = NULL; - sqlservercount = 0; -} - -void SQL_DeInit(void) -{ - sqlavailable = false; - - SQL_KillServers(); - - mysql_library_end(); - - mysql_dll_close(); + SQL_ServerCycle(svprogfuncs, pr_globals); } #endif @@ -6128,7 +5352,6 @@ void SQL_DeInit(void) - int PR_EnableEBFSBuiltin(char *name, int binum) { int i; @@ -6803,11 +6026,6 @@ static void QCBUILTIN PF_clientcommand (progfuncs_t *prinst, struct globalvars_s } - - - - - static void QCBUILTIN PF_h2AdvanceFrame(progfuncs_t *prinst, struct globalvars_s *pr_globals) { edict_t *Ent; diff --git a/engine/server/sv_sql.c b/engine/server/sv_sql.c new file mode 100644 index 000000000..f3cb481d6 --- /dev/null +++ b/engine/server/sv_sql.c @@ -0,0 +1,813 @@ +#include "quakedef.h" + +#ifdef SQL +#include "sv_sql.h" + +static void (VARGS *qmysql_library_end)(void); +static int (VARGS *qmysql_library_init)(int argc, char **argv, char **groups); + +static my_ulonglong (VARGS *qmysql_affected_rows)(MYSQL *mysql); +static void (VARGS *qmysql_close)(MYSQL *sock); +static void (VARGS *qmysql_data_seek)(MYSQL_RES *result, my_ulonglong offset); +static unsigned int (VARGS *qmysql_errno)(MYSQL *mysql); +static const char *(VARGS *qmysql_error)(MYSQL *mysql); +static MYSQL_FIELD *(VARGS *qmysql_fetch_field_direct)(MYSQL_RES *res, unsigned int fieldnr); +static MYSQL_ROW (VARGS *qmysql_fetch_row)(MYSQL_RES *result); +static unsigned int (VARGS *qmysql_field_count)(MYSQL *mysql); +static void (VARGS *qmysql_free_result)(MYSQL_RES *result); +static const char *(VARGS *qmysql_get_client_info)(void); +static MYSQL *(VARGS *qmysql_init)(MYSQL *mysql); +static MYSQL_RES *(VARGS *qmysql_store_result)(MYSQL *mysql); +static unsigned int (VARGS *qmysql_num_fields)(MYSQL_RES *res); +static my_ulonglong (VARGS *qmysql_num_rows)(MYSQL_RES *res); +static int (VARGS *qmysql_options)(MYSQL *mysql, enum mysql_option option, const char *arg); +static int (VARGS *qmysql_query)(MYSQL *mysql, const char *q); +static MYSQL *(VARGS *qmysql_real_connect)(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag); +static unsigned long (VARGS *qmysql_real_escape_string)(MYSQL *mysql, char *to, const char *from, unsigned long length); +static void (VARGS *qmysql_thread_end)(void); +static my_bool (VARGS *qmysql_thread_init)(void); +static unsigned int (VARGS *qmysql_thread_safe)(void); + +static dllfunction_t mysqlfuncs[] = +{ + {(void*)&qmysql_library_end, "mysql_server_end"}, /* written as a define alias in mysql.h */ + {(void*)&qmysql_library_init, "mysql_server_init"}, /* written as a define alias in mysql.h */ + {(void*)&qmysql_affected_rows, "mysql_affected_rows"}, + {(void*)&qmysql_close, "mysql_close"}, + {(void*)&qmysql_data_seek, "mysql_data_seek"}, + {(void*)&qmysql_errno, "mysql_errno"}, + {(void*)&qmysql_error, "mysql_error"}, + {(void*)&qmysql_fetch_field_direct, "mysql_fetch_field_direct"}, + {(void*)&qmysql_fetch_row, "mysql_fetch_row"}, + {(void*)&qmysql_field_count, "mysql_field_count"}, + {(void*)&qmysql_free_result, "mysql_free_result"}, + {(void*)&qmysql_get_client_info, "mysql_get_client_info"}, + {(void*)&qmysql_init, "mysql_init"}, + {(void*)&qmysql_store_result, "mysql_store_result"}, + {(void*)&qmysql_num_fields, "mysql_num_fields"}, + {(void*)&qmysql_num_rows, "mysql_num_rows"}, + {(void*)&qmysql_options, "mysql_options"}, + {(void*)&qmysql_query, "mysql_query"}, + {(void*)&qmysql_real_connect, "mysql_real_connect"}, + {(void*)&qmysql_real_escape_string, "mysql_real_escape_string"}, + {(void*)&qmysql_thread_end, "mysql_thread_end"}, + {(void*)&qmysql_thread_init, "mysql_thread_init"}, + {(void*)&qmysql_thread_safe, "mysql_thread_safe"}, + {NULL} +}; + +cvar_t sql_driver = SCVARF("sv_sql_driver", "mysql", CVAR_NOUNSAFEEXPAND); +cvar_t sql_host = SCVARF("sv_sql_host", "127.0.0.1", CVAR_NOUNSAFEEXPAND); +cvar_t sql_username = SCVARF("sv_sql_username", "", CVAR_NOUNSAFEEXPAND); +cvar_t sql_password = SCVARF("sv_sql_password", "", CVAR_NOUNSAFEEXPAND); +cvar_t sql_defaultdb = SCVARF("sv_sql_defaultdb", "", CVAR_NOUNSAFEEXPAND); + +void SQL_PushResult(sqlserver_t *server, queryresult_t *qres) +{ + Sys_LockMutex(server->resultlock); + qres->next = NULL; + if (!server->resultslast) + server->results = server->resultslast = qres; + else + server->resultslast = server->resultslast->next = qres; + Sys_UnlockMutex(server->resultlock); +} + +queryresult_t *SQL_PullResult(sqlserver_t *server) +{ + queryresult_t *qres; + Sys_LockMutex(server->resultlock); + qres = server->results; + if (qres) + { + server->results = qres->next; + if (!server->results) + server->resultslast = NULL; + } + Sys_UnlockMutex(server->resultlock); + + return qres; +} + +void SQL_PushRequest(sqlserver_t *server, queryrequest_t *qreq) +{ + Sys_LockConditional(server->requestcondv); + qreq->next = NULL; + if (!server->requestslast) + server->requests = server->requestslast = qreq; + else + server->requestslast = server->requestslast->next = qreq; + Sys_UnlockConditional(server->requestcondv); +} + +queryrequest_t *SQL_PullRequest(sqlserver_t *server, qboolean lock) +{ + queryrequest_t *qreq; + if (lock) + Sys_LockConditional(server->requestcondv); + qreq = server->requests; + if (qreq) + { + server->requests = qreq->next; + if (!server->requests) + server->requestslast = NULL; + } + Sys_UnlockConditional(server->requestcondv); + + return qreq; +} + +sqlserver_t **sqlservers; +int sqlservercount; +qboolean sqlavailable; + +int sql_serverworker(void *sref) +{ + sqlserver_t *server = (sqlserver_t *)sref; + char *error = NULL; + my_bool reconnect = 1; + int tinit, i; + qboolean needlock = false; + + if (tinit = qmysql_thread_init()) + error = "MYSQL thread init failed"; + else if (!(server->mysql = qmysql_init(NULL))) + error = "MYSQL init failed"; + else if (qmysql_options(server->mysql, MYSQL_OPT_RECONNECT, &reconnect)) + error = "MYSQL reconnect options set failed"; + else + { + int port = 0; + char *colon; + + colon = strchr(server->connectparams[0], ':'); + if (colon) + { + *colon = '\0'; + port = atoi(colon + 1); + } + + if (!(server->mysql = qmysql_real_connect(server->mysql, server->connectparams[0], server->connectparams[1], server->connectparams[2], server->connectparams[3], port, 0, 0))) + error = "MYSQL initial connect attempt failed"; + + if (colon) + *colon = ':'; + } + + for (i = SQL_CONNECT_STRUCTPARAMS; i < SQL_CONNECT_PARAMS; i++) + Z_Free(server->connectparams[i]); + + BZ_Realloc(server->connectparams, sizeof(char *) * SQL_CONNECT_STRUCTPARAMS); + + if (error) + server->active = false; + + while (server->active) + { + Sys_LockConditional(server->requestcondv); + if (!server->requests) // this is needed for thread startup and to catch any "lost" changes + Sys_ConditionWait(server->requestcondv); + needlock = false; // so we don't try to relock first round + + while (1) + { + queryrequest_t *qreq = NULL; + queryresult_t *qres; + const char *qerror = NULL; + MYSQL_RES *mysqlres = NULL; + int rows = -1; + int columns = -1; + int qesize = 0; + + if (!(qreq = SQL_PullRequest(server, needlock))) + break; + + // pullrequest makes sure our condition is unlocked but we'll need + // a lock next round + needlock = true; + + // perform the query and fill out the result structure + if (qmysql_query(server->mysql, qreq->query)) + qerror = qmysql_error(server->mysql); + else // query succeeded + { + mysqlres = qmysql_store_result(server->mysql); + if (mysqlres) // result set returned + { + rows = qmysql_num_rows(mysqlres); + columns = qmysql_num_fields(mysqlres); + } + else if (qmysql_field_count(server->mysql) == 0) // no result set + { + rows = qmysql_affected_rows(server->mysql); + if (rows < 0) + rows = 0; + columns = 0; + } + else // error + qerror = qmysql_error(server->mysql); + } + + if (qerror) + qesize = Q_strlen(qerror); + qres = (queryresult_t *)ZF_Malloc(sizeof(queryresult_t) + qesize); + if (qres) + { + if (qerror) + Q_strncpy(qres->error, qerror, qesize); + qres->result = mysqlres; + qres->rows = rows; + qres->columns = columns; + qres->request = qreq; + qres->eof = true; // store result has no more rows to read afterwards + qreq->next = NULL; + + SQL_PushResult(server, qres); + } + else // we're screwed here so bomb out + { + server->active = false; + error = "MALLOC ERROR! Unable to allocate query result!"; + break; + } + } + } + + if (server->mysql) + qmysql_close(server->mysql); + + // if we have a server error we still need to put it on the queue + if (error) + { + int esize = Q_strlen(error); + queryresult_t *qres = (queryresult_t *)Z_Malloc(sizeof(queryresult_t) + esize); + if (qres) + { // hopefully the qmysql_close gained us some memory otherwise we're pretty screwed + qres->rows = qres->columns = -1; + Q_strncpy(qres->error, error, esize); + + SQL_PushResult(server, qres); + } + } + + if (!tinit) + qmysql_thread_end(); + + return 0; +} + +sqlserver_t *SQL_GetServer (int serveridx, qboolean inactives) +{ + if (serveridx < 0 || serveridx >= sqlservercount) + return NULL; + if (!inactives && sqlservers[serveridx]->active == false) + return NULL; + return sqlservers[serveridx]; +} + +queryresult_t *SQL_GetQueryResult (sqlserver_t *server, int queryidx) +{ + queryresult_t *qres; + + qres = server->currentresult; + if (qres && qres->request && qres->request->num == queryidx) + return qres; + + for (qres = server->persistresults; qres; qres = qres->next) + if (qres->request && qres->request->num == queryidx) + return qres; + + return NULL; +} + +void SQL_DeallocResult(queryresult_t *qres) +{ + // deallocate current result + if (qres->result) + qmysql_free_result(qres->result); + if (qres->request) + Z_Free(qres->request); + + Z_Free(qres); +} + +void SQL_ClosePersistantResult(sqlserver_t *server, queryresult_t *qres) +{ + queryresult_t *prev, *cur; + + prev = server->persistresults; + if (prev == qres) + { + server->persistresults = prev->next; + SQL_DeallocResult(prev); + return; + } + + for (cur = prev->next; cur; prev = cur, cur = prev->next) + { + if (cur == qres) + { + prev = cur->next; + SQL_DeallocResult(cur); + return; + } + } +} + +void SQL_CloseResult(sqlserver_t *server, queryresult_t *qres) +{ + if (!qres) + return; + if (qres == server->currentresult) + { + SQL_DeallocResult(server->currentresult); + server->currentresult = NULL; + return; + } + // else we have a persistant query + SQL_ClosePersistantResult(server, qres); +} + +void SQL_CloseAllResults(sqlserver_t *server) +{ + queryresult_t *oldqres, *qres; + + // close orphaned results (we assume the lock is active or non-existant at this point) + qres = server->results; + while (qres) + { + oldqres = qres; + qres = qres->next; + SQL_DeallocResult(oldqres); + } + // close current + if (server->currentresult) + { + SQL_DeallocResult(server->currentresult); + server->currentresult = NULL; + } + // close persistant results + qres = server->persistresults; + while (qres) + { + oldqres = qres; + qres = qres->next; + SQL_DeallocResult(oldqres); + } + server->persistresults = NULL; + // close server result + if (server->serverresult) + { + SQL_DeallocResult(server->serverresult); + server->serverresult = NULL; + } +} + +char *SQL_ReadField (sqlserver_t *server, queryresult_t *qres, int row, int col, qboolean fields) +{ + if (!qres->result) // TODO: partial resultset logic not implemented yet + return NULL; + else + { // store_result query + if (qres->rows < row || qres->columns < col || col < 0) + return NULL; + + if (row < 0) + { // fetch field name + if (fields) // but only if we asked for them + { + MYSQL_FIELD *field; + + field = qmysql_fetch_field_direct(qres->result, col); + + if (!field) + return NULL; + else + return field->name; + } + else + return NULL; + } + else + { // fetch data + MYSQL_ROW sqlrow; + + qmysql_data_seek(qres->result, row); + sqlrow = qmysql_fetch_row(qres->result); + if (!sqlrow || !sqlrow[col]) + return NULL; + else + return sqlrow[col]; + } + } +} + +int SQL_NewServer(char *driver, char **paramstr) +{ + sqlserver_t *server; + int serverref; + int drvchoice; + int paramsize[SQL_CONNECT_PARAMS]; + int i, tsize; + + for (i = 0; i < SQL_CONNECT_PARAMS; i++) + paramsize[i] = Q_strlen(paramstr[i]); + + if (Q_strcasecmp(driver, "mysql") == 0) + drvchoice = SQLDRV_MYSQL; + else if (Q_strcasecmp(driver, "sqlite") == 0) + return -1; // drvchoice = SQLDRV_SQLITE; + else // invalid driver choice so we bomb out + return -1; + + // alloc or realloc sql servers array + if (sqlservers == NULL) + { + serverref = 0; + sqlservercount = 1; + sqlservers = (sqlserver_t **)BZ_Malloc(sizeof(sqlserver_t *)); + } + else + { + serverref = sqlservercount; + sqlservercount++; + sqlservers = (sqlserver_t **)BZ_Realloc(sqlservers, sizeof(sqlserver_t *) * sqlservercount); + } + + // assemble server structure + tsize = 0; + for (i = 0; i < SQL_CONNECT_STRUCTPARAMS; i++) + tsize += paramsize[i] + 1; // allocate extra space for host and user only + + server = (sqlserver_t *)Z_Malloc(sizeof(sqlserver_t) + tsize); + server->connectparams = (char **)BZ_Malloc(sizeof(char *) * SQL_CONNECT_PARAMS); + + tsize = 0; + for (i = 0; i < SQL_CONNECT_STRUCTPARAMS; i++) + { + server->connectparams[i] = ((char *)(server + 1)) + tsize; + Q_strncpy(server->connectparams[i], paramstr[i], paramsize[i]); + // string should be null-terminated due to Z_Malloc + tsize += paramsize[i] + 1; + } + for (i = SQL_CONNECT_STRUCTPARAMS; i < SQL_CONNECT_PARAMS; i++) + { + server->connectparams[i] = (char *)Z_Malloc(sizeof(char) * (paramsize[i] + 1)); + Q_strncpy(server->connectparams[i], paramstr[i], paramsize[i]); + // string should be null-terminated due to Z_Malloc + } + + sqlservers[serverref] = server; + + server->driver = (sqldrv_t)drvchoice; + server->querynum = 1; + server->active = true; + server->requestcondv = Sys_CreateConditional(); + server->resultlock = Sys_CreateMutex(); + + if (!server->requestcondv || !server->resultlock) + { + if (server->requestcondv) + Sys_DestroyConditional(server->requestcondv); + if (server->resultlock) + Sys_DestroyMutex(server->resultlock); + Z_Free(server); + sqlservercount--; + return -1; + } + + server->thread = Sys_CreateThread(sql_serverworker, (void *)server, 1024); + + if (!server->thread) + { + Z_Free(server); + sqlservercount--; + return -1; + } + + return serverref; +} + +int SQL_NewQuery(sqlserver_t *server, int callfunc, int type, int self, float selfid, int other, float otherid, char *str) +{ + int qsize = Q_strlen(str); + queryrequest_t *qreq; + int querynum; + + qreq = (queryrequest_t *)ZF_Malloc(sizeof(queryrequest_t) + qsize); + if (qreq) + { + qreq->persistant = (type == 1); + qreq->callback = callfunc; + + qreq->selfent = self; + qreq->selfid = selfid; + qreq->otherent = other; + qreq->otherid = otherid; + + querynum = qreq->num = server->querynum; + // prevent the reference num from getting too big to prevent FP problems + if (++server->querynum > 1000000) + server->querynum = 1; + + Q_strncpy(qreq->query, str, qsize); + + SQL_PushRequest(server, qreq); + Sys_ConditionSignal(server->requestcondv); + + return querynum; + } + + return -1; +} + +void SQL_Disconnect(sqlserver_t *server) +{ + server->active = false; + + // force the threads to reiterate requests and hopefully terminate + Sys_ConditionBroadcast(server->requestcondv); +} + +void SQL_Escape(sqlserver_t *server, char *src, char *dst, int dstlen) +{ + int srclen; + + switch (server->driver) + { + case SQLDRV_MYSQL: + srclen = strlen(dst); + if (srclen > (dstlen / 2 - 1)) + dst[0] = '\0'; + qmysql_real_escape_string(server->mysql, dst, src, srclen); + break; + default: + dst[0] = '\0'; + } +} + +char *SQL_Info(sqlserver_t *server) +{ + switch (server->driver) + { + case SQLDRV_MYSQL: + return va("mysql: %s", qmysql_get_client_info()); + break; + default: + return "unknown"; + } +} + +qboolean SQL_Available(void) +{ + return sqlavailable; +} + +/* SQL related commands */ +void SQL_Status_f(void) +{ + int i; + + Con_Printf("%i connections\n", sqlservercount); + for (i = 0; i < sqlservercount; i++) + { + int reqnum = 0; + int resnum = 0; + queryrequest_t *qreq; + queryresult_t *qres; + + sqlserver_t *server = sqlservers[i]; + + Sys_LockMutex(server->resultlock); + Sys_LockConditional(server->requestcondv); + for (qreq = server->requests; qreq; qreq = qreq->next) + reqnum++; + for (qres = server->results; qres; qres = qres->next) + resnum++; + + Con_Printf("#%i %s@%s: %s\n", + i, + server->connectparams[1], + server->connectparams[0], + server->active ? "active" : "inactive"); + + if (reqnum) + { + Con_Printf ("- %i requests\n"); + for (qreq = server->requests; qreq; qreq = qreq->next) + { + Con_Printf (" query #%i: %s\n", + qreq->num, + qreq->query); + // TODO: function lookup? + } + } + + if (resnum) + { + Con_Printf ("- %i results\n"); + for (qres = server->results; qres; qres = qres->next) + { + Con_Printf (" * %i rows, %i columns", + qres->rows, + qres->columns); + if (qres->error[0]) + Con_Printf(", error %s\n", qres->error); + else + Con_Printf("\n"); + // TODO: request info? + } + } + + if (server->serverresult) + Con_Printf ("server result: error %s\n", server->serverresult->error); + + // TODO: list all requests, results here + Sys_UnlockMutex(server->resultlock); + Sys_UnlockConditional(server->requestcondv); + } +} + +void SQL_Kill_f (void) +{ + sqlserver_t *server; + + if (Cmd_Argc() < 2) + { + Con_Printf ("Syntax: %s serverid\n", Cmd_Argv(0)); + return; + } + + server = SQL_GetServer(atoi(Cmd_Argv(1)), false); + if (server) + { + server->active = false; + Sys_ConditionBroadcast(server->requestcondv); + return; + } +} + +void SQL_Killall_f (void) +{ + SQL_KillServers(); +} + +void SQL_ServerCycle (progfuncs_t *prinst, struct globalvars_s *pr_globals) +{ + int i; + + for (i = 0; i < sqlservercount; i++) + { + sqlserver_t *server = sqlservers[i]; + queryresult_t *qres; + + while (qres = SQL_PullResult(server)) + { + qres->next = NULL; + if (qres->request && qres->request->callback) + { + if (server->active) + { // only process results to callback if server is active + edict_t *ent; + + server->currentresult = qres; + G_FLOAT(OFS_PARM0) = i; + G_FLOAT(OFS_PARM1) = qres->request->num; + G_FLOAT(OFS_PARM2) = qres->rows; + G_FLOAT(OFS_PARM3) = qres->columns; + G_FLOAT(OFS_PARM4) = qres->eof; + + // recall self and other references + ent = PROG_TO_EDICT(prinst, qres->request->selfent); + if (ent->isfree || ent->xv->uniquespawnid != qres->request->selfid) + pr_global_struct->self = pr_global_struct->world; + else + pr_global_struct->self = qres->request->selfent; + ent = PROG_TO_EDICT(prinst, qres->request->otherent); + if (ent->isfree || ent->xv->uniquespawnid != qres->request->otherid) + pr_global_struct->other = pr_global_struct->world; + else + pr_global_struct->other = qres->request->otherent; + + PR_ExecuteProgram(prinst, qres->request->callback); + + if (qres->eof) + { + if (server->currentresult) + { + if (server->currentresult->request && server->currentresult->request->persistant) + { + // move into persistant list + server->currentresult->next = server->persistresults; + server->persistresults = server->currentresult; + } + else // just close the query + SQL_CloseResult(server, server->currentresult); + } + } + // TODO: else we move a request back into the queue? + } + } + else // error or server-only result + { + if (server->serverresult) + Z_Free(server->serverresult); + server->serverresult = qres; + } + } + server->currentresult = NULL; + } +} + +void SQL_MYSQLInit(void) +{ + if (!(mysqlhandle = Sys_LoadLibrary("libmysql", mysqlfuncs))) + { + Con_Printf("mysql client didn't load\n"); + return; + } + + if (qmysql_thread_safe()) + { + if (!qmysql_library_init(0, NULL, NULL)) + { + Con_Printf("MYSQL backend loaded\n"); + sqlavailable = true; + return; + } + else + Con_Printf("MYSQL library init failed!\n"); + } + else + Con_Printf("MYSQL client is not thread safe!\n"); + + Sys_CloseLibrary(mysqlhandle); + sqlavailable = false; +} + +void SQL_Init(void) +{ + Cmd_AddCommand ("sqlstatus", SQL_Status_f); + Cmd_AddCommand ("sqlkill", SQL_Kill_f); + Cmd_AddCommand ("sqlkillall", SQL_Killall_f); + + Cvar_Register(&sql_driver, SQLCVAROPTIONS); + Cvar_Register(&sql_host, SQLCVAROPTIONS); + Cvar_Register(&sql_username, SQLCVAROPTIONS); + Cvar_Register(&sql_password, SQLCVAROPTIONS); + Cvar_Register(&sql_defaultdb, SQLCVAROPTIONS); + + SQL_MYSQLInit(); +} + +void SQL_KillServers(void) +{ + int i; + for (i = 0; i < sqlservercount; i++) + { + sqlserver_t *server = sqlservers[i]; + queryrequest_t *qreq, *oldqreq; + + server->active = false; // set thread to kill itself + Sys_ConditionBroadcast(server->requestcondv); // force condition check + Sys_WaitOnThread(server->thread); // wait on thread to die + + // server resource deallocation (TODO: should this be done in the thread itself?) + Sys_DestroyConditional(server->requestcondv); + Sys_DestroyMutex(server->resultlock); + + // close orphaned requests + qreq = server->requests; + while (qreq) + { + oldqreq = qreq; + qreq = qreq->next; + Z_Free(oldqreq); + } + + SQL_CloseAllResults(server); + + // the alloc'ed connect params should get deallocated by the thread + if (server->connectparams) + BZ_Free(server->connectparams); + + Z_Free(server); + } + if (sqlservers) + Z_Free(sqlservers); + sqlservers = NULL; + sqlservercount = 0; +} + +void SQL_DeInit(void) +{ + sqlavailable = false; + + SQL_KillServers(); + + qmysql_library_end(); + + Sys_CloseLibrary(mysqlhandle); +} + +#endif diff --git a/engine/server/sv_sql.h b/engine/server/sv_sql.h new file mode 100644 index 000000000..d6c8a04b4 --- /dev/null +++ b/engine/server/sv_sql.h @@ -0,0 +1,91 @@ +#ifndef MYSQLDLL_H +#define MYSQLDLL_H +#include + +dllhandle_t *mysqlhandle; + +#define SQL_CONNECT_STRUCTPARAMS 2 +#define SQL_CONNECT_PARAMS 4 + +typedef enum +{ + SQLDRV_MYSQL, + SQLDRV_SQLITE, /* NOT IN YET */ + SQLDRV_INVALID +} sqldrv_t; + +typedef struct queryrequest_s +{ + int num; /* query number reference */ + qboolean persistant; /* persistant query */ + struct queryrequest_s *next; /* next request in queue */ + int callback; /* callback function reference */ + int selfent; /* self entity on call */ + float selfid; /* self entity id on call */ + int otherent; /* other entity on call */ + float otherid; /* other entity id on call */ + char query[1]; /* query to run */ +} queryrequest_t; + +typedef struct queryresult_s +{ + struct queryrequest_s *request; /* corresponding request */ + struct queryresult_s *next; /* next result in queue */ + int rows; /* rows contained in single result set */ + int columns; /* fields */ + qboolean eof; /* end of query reached */ + MYSQL_RES *result; /* result set from mysql */ +#if 0 + char **resultset; /* stored result set from partial fetch */ +#endif + char error[1]; /* error string, "" if none */ +} queryresult_t; + +typedef struct sqlserver_s +{ + void *thread; /* worker thread for server */ + sqldrv_t driver; /* driver type */ + MYSQL *mysql; /* mysql server */ + volatile qboolean active; /* set to false to kill thread */ + void *requestcondv; /* lock and conditional variable for queue read/write */ + void *resultlock; /* mutex for queue read/write */ + int querynum; /* next reference number for queries */ + queryrequest_t *requests; /* query requests queue */ + queryrequest_t *requestslast; /* query requests queue last link */ + queryresult_t *results; /* query results queue */ + queryresult_t *resultslast; /* query results queue last link */ + queryresult_t *currentresult; /* current called result */ + queryresult_t *persistresults; /* list of persistant results */ + queryresult_t *serverresult; /* server error results */ + char **connectparams; /* connect parameters (0 = host, 1 = user, 2 = pass, 3 = defaultdb) */ +} sqlserver_t; + +/* prototypes */ +void SQL_Init(void); +void SQL_KillServers(void); +void SQL_DeInit(void); + +sqlserver_t *SQL_GetServer (int serveridx, qboolean inactives); +queryresult_t *SQL_GetQueryResult (sqlserver_t *server, int queryidx); +void SQL_DeallocResult(queryresult_t *qres); +void SQL_ClosePersistantResult(sqlserver_t *server, queryresult_t *qres); +void SQL_CloseResult(sqlserver_t *server, queryresult_t *qres); +void SQL_CloseAllResults(sqlserver_t *server); +char *SQL_ReadField (sqlserver_t *server, queryresult_t *qres, int row, int col, qboolean fields); +int SQL_NewServer(char *driver, char **paramstr); +int SQL_NewQuery(sqlserver_t *server, int callfunc, int type, int self, float selfid, int other, float otherid, char *str); +void SQL_Disconnect(sqlserver_t *server); +void SQL_Escape(sqlserver_t *server, char *src, char *dst, int dstlen); +char *SQL_Info(sqlserver_t *server); +qboolean SQL_Available(void); +void SQL_ServerCycle (progfuncs_t *prinst, struct globalvars_s *pr_globals); + +extern cvar_t sql_driver; +extern cvar_t sql_host; +extern cvar_t sql_username; +extern cvar_t sql_password; +extern cvar_t sql_defaultdb; + +#define SQLCVAROPTIONS "SQL Defaults" + +#endif \ No newline at end of file diff --git a/engine/server/win_mysql.c b/engine/server/win_mysql.c deleted file mode 100644 index e18643a46..000000000 --- a/engine/server/win_mysql.c +++ /dev/null @@ -1,98 +0,0 @@ -#include "qwsvdef.h" - -#ifdef SQL - -#include "win_mysql.h" - -MYSQLDLL_FUNC1(my_ulonglong, mysql_affected_rows, MYSQL *) -MYSQLDLL_FUNC2(my_bool, mysql_autocommit, MYSQL *, my_bool) -MYSQLDLL_FUNC4(my_bool, mysql_change_user, MYSQL *, const char *, const char *, const char *) -MYSQLDLL_NORETFUNC1(mysql_close, MYSQL *) -MYSQLDLL_FUNC1(my_bool, mysql_commit, MYSQL *) -MYSQLDLL_NORETFUNC2(mysql_data_seek, MYSQL_RES *, my_ulonglong) -MYSQLDLL_FUNC1(int, mysql_dump_debug_info, MYSQL *) -MYSQLDLL_FUNC1(unsigned int, mysql_errno, MYSQL *) -MYSQLDLL_FUNC1(const char *, mysql_error, MYSQL *) -MYSQLDLL_FUNC1(MYSQL_FIELD *, mysql_fetch_field, MYSQL_RES *) -MYSQLDLL_FUNC2(MYSQL_FIELD *, mysql_fetch_field_direct, MYSQL_RES *, unsigned int) -MYSQLDLL_FUNC1(MYSQL_FIELD *, mysql_fetch_fields, MYSQL_RES *) -MYSQLDLL_FUNC1(unsigned long *, mysql_fetch_lengths, MYSQL_RES *) -MYSQLDLL_FUNC1(MYSQL_ROW, mysql_fetch_row, MYSQL_RES *) -MYSQLDLL_FUNC2(MYSQL_FIELD_OFFSET, mysql_field_seek, MYSQL_RES *, MYSQL_FIELD_OFFSET) -MYSQLDLL_FUNC1(unsigned int, mysql_field_count, MYSQL *) -MYSQLDLL_FUNC1(MYSQL_FIELD_OFFSET, mysql_field_tell, MYSQL_RES *) -MYSQLDLL_NORETFUNC1(mysql_free_result, MYSQL_RES *) -MYSQLDLL_FUNC0(const char *, mysql_get_client_info) -MYSQLDLL_FUNC0(unsigned long, mysql_get_client_version) -MYSQLDLL_FUNC1(const char *, mysql_get_host_info, MYSQL *) -MYSQLDLL_FUNC1(unsigned long, mysql_get_server_version, MYSQL *) -MYSQLDLL_FUNC1(unsigned int, mysql_get_proto_info, MYSQL *) -MYSQLDLL_FUNC1(const char *, mysql_get_server_info, MYSQL *) -MYSQLDLL_FUNC1(const char *, mysql_info, MYSQL *) -MYSQLDLL_FUNC1(MYSQL *, mysql_init, MYSQL *) -MYSQLDLL_FUNC1(my_ulonglong, mysql_insert_id, MYSQL *) -MYSQLDLL_FUNC2(int, mysql_kill, MYSQL *, unsigned long) -MYSQLDLL_NORETFUNC0(mysql_server_end) -MYSQLDLL_FUNC3(int, mysql_server_init, int, char **, char **) -MYSQLDLL_FUNC2(MYSQL_RES *, mysql_list_dbs, MYSQL *, const char *) -MYSQLDLL_FUNC3(MYSQL_RES *, mysql_list_fields, MYSQL *, const char *, const char *) -MYSQLDLL_FUNC1(MYSQL_RES *, mysql_list_processes, MYSQL *) -MYSQLDLL_FUNC2(MYSQL_RES *, mysql_list_tables, MYSQL *, const char *) -MYSQLDLL_FUNC1(my_bool, mysql_more_results, MYSQL *) -MYSQLDLL_FUNC1(int, mysql_next_result, MYSQL *) -MYSQLDLL_FUNC1(unsigned int, mysql_num_fields, MYSQL_RES *) -MYSQLDLL_FUNC1(my_ulonglong, mysql_num_rows, MYSQL_RES *) -MYSQLDLL_FUNC3(int, mysql_options, MYSQL *, enum mysql_option, const char *) -MYSQLDLL_FUNC1(int, mysql_ping, MYSQL *) -MYSQLDLL_FUNC2(int, mysql_query, MYSQL *, const char *) -MYSQLDLL_FUNC8(MYSQL *, mysql_real_connect, MYSQL *, const char *, const char *, const char *, const char *, unsigned int, const char *, unsigned long) -MYSQLDLL_FUNC4(unsigned long, mysql_real_escape_string, MYSQL *, char *, const char *, unsigned long) -MYSQLDLL_FUNC3(int, mysql_real_query, MYSQL *, const char *, unsigned long) -MYSQLDLL_FUNC2(int, mysql_refresh, MYSQL *, unsigned int) -// MYSQLDLL_FUNC1(int, mysql_reload, MYSQL *) -MYSQLDLL_FUNC1(my_bool, mysql_rollback, MYSQL *) -MYSQLDLL_FUNC2(MYSQL_ROW_OFFSET, mysql_row_seek, MYSQL_RES *, MYSQL_ROW_OFFSET) -MYSQLDLL_FUNC1(MYSQL_ROW_OFFSET, mysql_row_tell, MYSQL_RES *) -MYSQLDLL_FUNC2(int, mysql_select_db, MYSQL *, const char *) -MYSQLDLL_FUNC2(int, mysql_set_server_option, MYSQL *, enum enum_mysql_set_option) -MYSQLDLL_FUNC1(const char *, mysql_sqlstate, MYSQL *) -MYSQLDLL_FUNC2(int, mysql_shutdown, MYSQL *, enum mysql_enum_shutdown_level) -MYSQLDLL_FUNC1(const char *, mysql_stat, MYSQL *) -MYSQLDLL_FUNC1(MYSQL_RES *, mysql_store_result, MYSQL *) -MYSQLDLL_NORETFUNC0(mysql_thread_end) -MYSQLDLL_FUNC1(unsigned long, mysql_thread_id, MYSQL *) -MYSQLDLL_FUNC0(my_bool, mysql_thread_init) -MYSQLDLL_FUNC0(unsigned int, mysql_thread_safe) -MYSQLDLL_FUNC1(MYSQL_RES *, mysql_use_result, MYSQL *) -MYSQLDLL_FUNC1(unsigned int, mysql_warning_count, MYSQL *) - -/* -Not doing this: - -void mysql_set_local_infile_default(MYSQL *mysql) -void mysql_set_local_infile_handler(MYSQL *mysql, - int (*local_infile_init)(void **, const char *, void *), - int (*local_infile_read)(void *, char *, unsigned int), - void (*local_infile_end)(void *), - int (*local_infile_error)(void *, char*, unsigned int), - void *userdata) -*/ - -int mysql_dll_init() -{ - mysqldll = LoadLibrary("libmysql.dll"); - - if (mysqldll == NULL) - return 0; - - return 1; -} - -int mysql_dll_close() -{ - if (mysqldll != NULL) - FreeLibrary(mysqldll); - - return 1; -} -#endif diff --git a/engine/server/win_mysql.h b/engine/server/win_mysql.h deleted file mode 100644 index ab2855260..000000000 --- a/engine/server/win_mysql.h +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef MYSQLDLL_H -#define MYSQLDLL_H - -#include - -HINSTANCE mysqldll; -int mysqldllerror; - -#define MYSQLDLL_NOCONV - -#define MYSQLDLL_LOADFUNC(funcname) \ - int loaded_##funcname = 0; \ - int funcload_##funcname() \ - { \ - if (mysqldll) \ - { \ - if (!loaded_##funcname) \ - { \ - cb_##funcname = (DLLFUNC_##funcname)GetProcAddress(mysqldll, #funcname); \ - loaded_##funcname = 1; \ - mysqldllerror = GetLastError(); \ - \ - } \ - return (cb_##funcname != NULL); \ - } \ - return 0; \ - } - -#define MYSQLDLL_FUNC0(returntype, funcname) \ - typedef returntype (CALLBACK *DLLFUNC_##funcname)(); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - returntype STDCALL funcname() \ - { \ - if (funcload_##funcname()) \ - return cb_##funcname(); \ - return (returntype)0; \ - } - -#define MYSQLDLL_FUNC1(returntype, funcname, paramtype1) \ - typedef returntype (CALLBACK *DLLFUNC_##funcname)(paramtype1); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - returntype STDCALL funcname(paramtype1 p1) \ - { \ - if (funcload_##funcname()) \ - return cb_##funcname(p1); \ - return (returntype)0; \ - } - -#define MYSQLDLL_FUNC2(returntype, funcname, paramtype1, paramtype2) \ - typedef returntype (CALLBACK *DLLFUNC_##funcname)(paramtype1, paramtype2); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - returntype STDCALL funcname(paramtype1 p1, paramtype2 p2) \ - { \ - if (funcload_##funcname()) \ - return cb_##funcname(p1, p2); \ - return (returntype)0; \ - } - -#define MYSQLDLL_FUNC3(returntype, funcname, paramtype1, paramtype2, paramtype3) \ - typedef returntype (CALLBACK *DLLFUNC_##funcname)(paramtype1, paramtype2, paramtype3); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - returntype STDCALL funcname(paramtype1 p1, paramtype2 p2, paramtype3 p3) \ - { \ - if (funcload_##funcname()) \ - return cb_##funcname(p1, p2, p3); \ - return (returntype)0; \ - } - -#define MYSQLDLL_FUNC4(returntype, funcname, paramtype1, paramtype2, paramtype3, paramtype4) \ - typedef returntype (CALLBACK *DLLFUNC_##funcname)(paramtype1, paramtype2, paramtype3, paramtype4); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - returntype STDCALL funcname(paramtype1 p1, paramtype2 p2, paramtype3 p3, paramtype4 p4) \ - { \ - if (funcload_##funcname()) \ - return cb_##funcname(p1, p2, p3, p4); \ - return (returntype)0; \ - } - -#define MYSQLDLL_FUNC8(returntype, funcname, paramtype1, paramtype2, paramtype3, paramtype4, paramtype5, paramtype6, paramtype7, paramtype8) \ - typedef returntype (CALLBACK *DLLFUNC_##funcname)(paramtype1, paramtype2, paramtype3, paramtype4, paramtype5, paramtype6, paramtype7, paramtype8); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - returntype STDCALL funcname(paramtype1 p1, paramtype2 p2, paramtype3 p3, paramtype4 p4, paramtype5 p5, paramtype6 p6, paramtype7 p7, paramtype8 p8) \ - { \ - if (funcload_##funcname()) \ - return cb_##funcname(p1, p2, p3, p4, p5, p6, p7, p8); \ - return (returntype)0; \ - } - -#define MYSQLDLL_NORETFUNC0(funcname) \ - typedef void (CALLBACK *DLLFUNC_##funcname)(); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - void STDCALL funcname() \ - { \ - if (funcload_##funcname()) \ - cb_##funcname(); \ - } - -#define MYSQLDLL_NORETFUNC1(funcname, paramtype1) \ - typedef void (CALLBACK *DLLFUNC_##funcname)(paramtype1); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - void STDCALL funcname(paramtype1 p1) \ - { \ - if (funcload_##funcname()) \ - cb_##funcname(p1); \ - } - -#define MYSQLDLL_NORETFUNC2(funcname, paramtype1, paramtype2) \ - typedef void (CALLBACK *DLLFUNC_##funcname)(paramtype1, paramtype2); \ - DLLFUNC_##funcname cb_##funcname; \ - MYSQLDLL_LOADFUNC(funcname) \ - void STDCALL funcname(paramtype1 p1, paramtype2 p2) \ - { \ - if (funcload_##funcname()) \ - cb_##funcname(p1, p2); \ - } - - -// prototypes -int mysql_dll_init(); -int mysql_dll_close(); - -#endif \ No newline at end of file