SQL: sqlreadfield with a negative row returns field name, server request/result stacks changed to queues, query references numbers limited to +/-50000, proper library init/end calls, sqlversion call

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@2942 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
TimeServ 2008-04-15 00:29:17 +00:00
parent 7f73771ba2
commit 9e7a43c96c

View file

@ -6338,6 +6338,7 @@ typedef struct queryresult_s
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)
@ -6348,16 +6349,76 @@ typedef struct sqlserver_s
void *thread; // worker thread for server
MYSQL *mysql; // mysql server
qboolean active; // set to false to kill thread
void *requestlock; // mutex for linked list read/write
void *resultlock; // mutex for linked list read/write
void *requestlock; // mutex for queue read/write
void *resultlock; // mutex for queue read/write
int querynum; // next reference number for queries
queryrequest_t *requests; // query requests
queryresult_t *results; // query results link
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 *serverresult; // server error results
char host[1]; // host (struct hack)
} 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)
{
Sys_UnlockMutex(server->resultlock);
return NULL;
}
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_LockMutex(server->requestlock);
qreq->next = NULL;
if (!server->requestslast)
server->requests = server->requestslast = qreq;
else
server->requestslast = server->requestslast->next = qreq;
Sys_UnlockMutex(server->requestlock);
}
queryrequest_t *SQL_PullRequest(sqlserver_t *server)
{
queryrequest_t *qreq;
Sys_LockMutex(server->requestlock);
qreq = server->requests;
if (!qreq)
{
Sys_UnlockMutex(server->requestlock);
return NULL;
}
server->requests = qreq->next;
if (!server->requests)
server->requestslast = NULL;
Sys_UnlockMutex(server->requestlock);
return qreq;
}
sqlserver_t **sqlservers;
int sqlservercount;
@ -6380,20 +6441,14 @@ int sql_serverworker(void *sref)
server->active = false;
while (server->active)
{
queryrequest_t *qreq = NULL;
// replace this with a conditional
if (server->requests)
{
Sys_LockMutex(server->requestlock);
qreq = server->requests;
server->requests = qreq->next;
Sys_UnlockMutex(server->requestlock);
}
{
// TODO: replace this with a conditional
if (!server->requests)
continue;
while (qreq)
while (1)
{
queryrequest_t *qreq = NULL;
queryresult_t *qres;
const char *qerror = NULL;
MYSQL_RES *mysqlres = NULL;
@ -6401,6 +6456,10 @@ int sql_serverworker(void *sref)
int columns = -1;
int qesize = 0;
if (!(qreq = SQL_PullRequest(server)))
break;
// perform the query and fill out the result structure
if (mysql_query(server->mysql, qreq->query))
qerror = mysql_error(server->mysql);
else // query succeeded
@ -6414,6 +6473,8 @@ int sql_serverworker(void *sref)
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
@ -6429,35 +6490,24 @@ int sql_serverworker(void *sref)
qres->rows = rows;
qres->columns = columns;
qres->request = qreq;
qres->eof = true; // store result has no more rows to read afterwards
qreq->next = NULL;
Sys_LockMutex(server->resultlock);
qres->next = server->results;
server->results = qres;
Sys_UnlockMutex(server->resultlock);
Sys_LockMutex(server->requestlock);
qreq = server->requests;
if (qreq)
server->requests = qreq->next;
Sys_UnlockMutex(server->requestlock);
SQL_PushResult(server, qres);
}
}
// 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);
qres->rows = qres->columns = -1;
Q_strncpy(qres->error, error, esize);
Sys_LockMutex(server->resultlock);
qres->next = server->results;
server->results = qres;
Sys_UnlockMutex(server->resultlock);
SQL_PushResult(server, qres);
}
mysql_close(server->mysql);
@ -6474,6 +6524,7 @@ void PF_sqlconnect (progfuncs_t *prinst, struct globalvars_s *pr_globals)
int hsize;
sqlserver_t *server;
// alloc or realloc sql servers array
if (sqlservers == NULL)
{
serverref = 0;
@ -6488,6 +6539,8 @@ void PF_sqlconnect (progfuncs_t *prinst, struct globalvars_s *pr_globals)
}
// TODO: need option logic for "default server" here
// assemble server structure
hoststr = PR_GetStringOfs(prinst, OFS_PARM0);
hsize = Q_strlen(hoststr);
@ -6534,20 +6587,34 @@ void PF_sqlopenquery (progfuncs_t *prinst, struct globalvars_s *pr_globals)
qreq->callback = callfunc;
querynum = qreq->num = sqlservers[serverref]->querynum;
sqlservers[serverref]->querynum++;
// prevent the reference num from getting too big to prevent FP problems
if (++sqlservers[serverref]->querynum > 50000)
sqlservers[serverref]->querynum -= 100000;
Q_strncpy(qreq->query, querystr, qsize);
// add into request queue
Sys_LockMutex(sqlservers[serverref]->requestlock);
qreq->next = sqlservers[serverref]->requests;
sqlservers[serverref]->requests = qreq;
Sys_UnlockMutex(sqlservers[serverref]->requestlock);
SQL_PushRequest(sqlservers[serverref], qreq);
// TODO: conditional trip here
G_FLOAT(OFS_RETURN) = querynum;
}
void SQL_CloseCurrentQuery(sqlserver_t *server)
{
if (!server->currentresult)
return;
// deallocate current result
if (server->currentresult->result)
mysql_free_result(server->currentresult->result);
if (server->currentresult->request)
Z_Free(server->currentresult->request);
Z_Free(server->currentresult);
server->currentresult = NULL;
}
void PF_sqlclosequery (progfuncs_t *prinst, struct globalvars_s *pr_globals)
{
int serverref = G_FLOAT(OFS_PARM0);
@ -6559,17 +6626,12 @@ void PF_sqlclosequery (progfuncs_t *prinst, struct globalvars_s *pr_globals)
!sqlservers[serverref]->currentresult ||
!sqlservers[serverref]->currentresult->request ||
sqlservers[serverref]->currentresult->request->num != queryref)
return;
return; // close query isn't for current query
// TODO: partial resultset logic not implemented yet
// TODO: should we allow closing queries out of scope?
// deallocate current result
if (sqlservers[serverref]->currentresult->result)
mysql_free_result(sqlservers[serverref]->currentresult->result);
if (sqlservers[serverref]->currentresult->request)
Z_Free(sqlservers[serverref]->currentresult->request);
Z_Free(sqlservers[serverref]->currentresult);
sqlservers[serverref]->currentresult = NULL;
SQL_CloseCurrentQuery(sqlservers[serverref]);
}
void PF_sqlreadfield (progfuncs_t *prinst, struct globalvars_s *pr_globals)
@ -6597,24 +6659,42 @@ void PF_sqlreadfield (progfuncs_t *prinst, struct globalvars_s *pr_globals)
}
else
{ // store_result query
MYSQL_ROW sqlrow;
if (sqlservers[serverref]->currentresult->rows < row ||
sqlservers[serverref]->currentresult->columns < col)
sqlservers[serverref]->currentresult->columns < col ||
col < 0)
{ // out of bounds
G_INT(OFS_RETURN) = 0;
return;
}
mysql_data_seek(sqlservers[serverref]->currentresult->result, row);
sqlrow = mysql_fetch_row(sqlservers[serverref]->currentresult->result);
if (!sqlrow || !sqlrow[col])
{
G_INT(OFS_RETURN) = 0;
return;
if (row < 0)
{ // fetch field name
MYSQL_FIELD *field;
field = mysql_fetch_field_direct(sqlservers[serverref]->currentresult->result, col);
if (!field)
{
G_INT(OFS_RETURN) = 0;
return;
}
else
RETURN_TSTRING(field->name);
}
else
RETURN_TSTRING(sqlrow[col]);
{ // fetch data
MYSQL_ROW sqlrow;
mysql_data_seek(sqlservers[serverref]->currentresult->result, row);
sqlrow = mysql_fetch_row(sqlservers[serverref]->currentresult->result);
if (!sqlrow || !sqlrow[col])
{
G_INT(OFS_RETURN) = 0;
return;
}
else
RETURN_TSTRING(sqlrow[col]);
}
}
}
@ -6623,20 +6703,20 @@ void PF_sqlerror (progfuncs_t *prinst, struct globalvars_s *pr_globals)
int serverref = G_FLOAT(OFS_PARM0);
if (serverref < 0 || serverref >= sqlservercount)
{
{ // invalid server reference
RETURN_TSTRING("");
return;
}
if (*svprogfuncs->callargc == 2)
{
{ // query-specific error request
int queryref = G_FLOAT(OFS_PARM1);
if (!sqlservers[serverref]->active ||
!sqlservers[serverref]->currentresult ||
!sqlservers[serverref]->currentresult->request ||
sqlservers[serverref]->currentresult->request->num != queryref)
{
{ // invalid query
RETURN_TSTRING("");
return;
}
@ -6644,9 +6724,9 @@ void PF_sqlerror (progfuncs_t *prinst, struct globalvars_s *pr_globals)
RETURN_TSTRING(sqlservers[serverref]->currentresult->error);
}
else
{
{ // server-specific error request
if (!sqlservers[serverref]->serverresult)
{
{ // no error result on server, return empty string
RETURN_TSTRING("");
return;
}
@ -6662,8 +6742,8 @@ void PF_sqlescape (progfuncs_t *prinst, struct globalvars_s *pr_globals)
toescape = PR_GetStringOfs(prinst, OFS_PARM1);
if (!*toescape || serverref < 0 || serverref >= sqlservercount || sqlservers[serverref]->active == false)
{
if (!toescape || !*toescape || serverref < 0 || serverref >= sqlservercount || sqlservers[serverref]->active == false)
{ // invalid string or server reference
RETURN_TSTRING("");
return;
}
@ -6673,6 +6753,20 @@ void PF_sqlescape (progfuncs_t *prinst, struct globalvars_s *pr_globals)
RETURN_TSTRING(escaped);
}
void PF_sqlversion (progfuncs_t *prinst, struct globalvars_s *pr_globals)
{
int serverref = G_FLOAT(OFS_PARM0);
if (serverref < 0 || serverref >= sqlservercount || sqlservers[serverref]->active == false)
{ // invalid string or server reference
RETURN_TSTRING("");
return;
}
// no other sql backends yet so just report mysql string for any active
RETURN_TSTRING(va("mysql: %s", mysql_get_client_info()));
}
void SQL_Cycle (progfuncs_t *prinst, struct globalvars_s *pr_globals)
{
int i;
@ -6684,15 +6778,9 @@ void SQL_Cycle (progfuncs_t *prinst, struct globalvars_s *pr_globals)
while (1)
{
Sys_LockMutex(server->resultlock);
qres = server->results;
if (!qres)
{
Sys_UnlockMutex(server->resultlock);
// get a result off of queue
if (!(qres = SQL_PullResult(server)))
break;
}
server->results = qres->next;
Sys_UnlockMutex(server->resultlock);
qres->next = NULL;
if (qres->request && qres->request->callback)
@ -6704,8 +6792,15 @@ void SQL_Cycle (progfuncs_t *prinst, struct globalvars_s *pr_globals)
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;
PR_ExecuteProgram(prinst, qres->request->callback);
if (qres->eof && server->currentresult)
{ // TODO: is this even worth complaining about?
Con_Printf("QC didn't close query %i: %s\n", qres->request->num, qres->request->query);
SQL_CloseCurrentQuery(server);
}
}
}
else // error or server-only result
@ -6746,6 +6841,12 @@ void SQL_Init()
Con_Printf("MYSQL client is not thread safe!\n");
// TODO: disable extension here
}
if (!mysql_library_init(0, NULL, NULL))
{
Con_Printf("MYSQL library init failed!\n");
// TODO: disable extension here
}
}
void SQL_DeInit()
@ -6788,6 +6889,8 @@ void SQL_DeInit()
Z_Free(qres);
}
SQL_CloseCurrentQuery(server);
if (server->serverresult)
Z_Free(server->serverresult);
@ -6798,6 +6901,8 @@ void SQL_DeInit()
sqlservers = NULL;
sqlservercount = 0;
mysql_library_end();
mysql_dll_close();
}
#endif
@ -6933,7 +7038,7 @@ lh_extension_t QSG_Extensions[] = {
#ifdef SQL
// serverside SQL functions for managing an SQL database connection
{"FTE_SQL", 7, NULL, {"sqlconnect","sqldisconnect","sqlopenquery","sqlclosequery","sqlreadfield","sqlerror","sqlescape"}},
{"FTE_SQL", 8, NULL, {"sqlconnect","sqldisconnect","sqlopenquery","sqlclosequery","sqlreadfield","sqlerror","sqlescape","sqlversion"}},
#endif
//eperimental advanced strings functions.
//reuses the FRIK_FILE builtins (with substring extension)
@ -10180,11 +10285,12 @@ BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
#ifdef SQL
{"sqlconnect", PF_sqlconnect, 0, 0, 0, 250}, // #250 float([string server]) sqlconnect (FTE_SQL)
{"sqldisconnect", PF_sqldisconnect, 0, 0, 0, 251}, // #251 void(float serveridx) sqldisconnect (FTE_SQL)
{"sqlopenquery", PF_sqlopenquery, 0, 0, 0, 252}, // #252 float(float serveridx, string query, void(float serveridx, float queryidx, float rows, float columns) callback) sqlopenquery (FTE_SQL)
{"sqlopenquery", PF_sqlopenquery, 0, 0, 0, 252}, // #252 float(float serveridx, string query, void(float serveridx, float queryidx, float rows, float columns, float eof) callback) sqlopenquery (FTE_SQL)
{"sqlclosequery", PF_sqlclosequery, 0, 0, 0, 253}, // #253 void(float serveridx, float queryidx) sqlclosequery (FTE_SQL)
{"sqlreadfield", PF_sqlreadfield, 0, 0, 0, 254}, // #254 string(float serveridx, float queryidx, float row, float column) sqlreadfield (FTE_SQL)
{"sqlerror", PF_sqlerror, 0, 0, 0, 255}, // #255 string(float serveridx, [float queryidx]) sqlerror (FTE_SQL)
{"sqlescape", PF_sqlescape, 0, 0, 0, 256}, // #256 string(float serveridx, string data) sqlescape (FTE_SQL)
{"sqlversion", PF_sqlversion, 0, 0, 0, 257}, // #257 string(float serveridx) sqlversion (FTE_SQL)
#endif
//EXT_CSQC