/* /secure/daemon/SQL.c * Author: Shadyman@QS * Date: 18-Aug-2008 * * For each block of SQL-ized code you write, you MUST * choose EITHER blocking OR non-blocking. You can't just * switch halfway through and expect it to work. * * BLOCKING SQL: * This file acts as an intermediary between the lib and * the driver's DB package to make things a little easier * to use/more foolproof/implementable with less code. * * Unfortunately, this driver code is "blocking", as in * it doesn't let any other mud processes run while it is * working on db_ SQL efuns. So use the BLOCKING code VERY * CAREFULLY and SPARINGLY. * * NON-BLOCKING SQL: * This code was written to take advantage of the * external_cmd efun code, and calls the "mysql" command * directly. However, it is non-blocking in that it lets * other mud processes get by while it is waiting for a * response. * * This option is recommended for when you either: * a) Don't care about a return value, as in INSERTs, * UPDATEs, DELETEs * b) CAN afford to wait for a CALLBACK * c) Are doing LOOOOOOONG queries. * d) Non mission-critical SQL statements, ie: Logging * e) If you want access to tables via a PASSWORDED user. * * It can be invoked whenever a function in this file has * an integer named "NONBLOCKING" by setting it to 1. * * You can also specify whether or not you want a callback * when there is a "CALLBACK" integer by setting it to 1. * * (Both of these options are optional) */ #include #include #include inherit LIB_DAEMON; //**NON-Blocking SQL defines //SQL is command number 1 in mudos.cfg #define CMD_NUM 1 //10 seconds for the non-blocking socket to time out should be more than enough //No timeout. We want the last socket to stay open until not needed. #define NB_TIMEOUT 8 //Do we want to use transactions? IE, COMMIT, ROLLBACK, etc? #define TRANSACT 1 //private static int _fd = -1; private static int _currentId; //private static int _callout_id; private static mapping _callbacks = ([ ]); //Mapping of ID to callback function private static mapping _requests = ([ ]); //Mapping of ID to SQL Statement //private static mapping socks = ([ ]); //Keeping track of open handles private static mapping data = ([ ]); private static string temppass = ""; private static string prevhost; private static string prevdb; private static string prevuser; private static string prevpass; private static int prevhandle; //**Shared defines //N/A //**NON-Blocking Prototypes varargs int sql_nb_connect(string host, string db, string user, string pass); int valid_nb_database(); varargs int make_nb_sql_request(int handle, string request, int callback); mapping nb_query_requests(); //int nb_isConnected(int handle); mixed ReformCharacters(string value); mixed *ParseXML(string resultset); void return_callback(int id, int errno, mixed msg); void DataIn(int handle, string msg); void read_call_back(int handle, mixed msg); void close_call_back(int handle); varargs int sql_nb_close(int handle, int calledout); void dest_me(); //**Shared Prototypes varargs int sql_connect (string server, string db, string username, string pass, int nonblocking); varargs mixed sql_query (int handle, string query, int nonblocking, mixed callback); varargs int sql_commit(int handle, int nonblocking); varargs int sql_rollback(int handle, int nonblocking); varargs string sql_status(int nonblocking); varargs int sql_close(int handle, int nonblocking, int callback); varargs mixed sql_quicksubmit (mixed query, string host, string db, string username, string pass); varargs mixed sql_quickcheck (mixed query, string host, string db, string username, string pass); int sql_start_transaction(int handle, int nonblocking); //**************** Shared Code ********************** /* varargs int SQL_CONNECT (string server, string db, * string username, string pass, int nonblocking) * Connects to the specified server and database, with * the given username, or uses defaults if none given. * All arguments are optional. Defaults from db.h * * NON-BLOCKING: * Returns the connection handle (int >=1). O or less is FAIL. * See "socket_err.h" for info on negative error codes. * BLOCKING: * Returns the connection handle (int >= 1). 0 is FAIL. */ varargs int sql_connect (string server, string db, string username, string pass, int nonblocking) { mixed foo; int handle; if (!server || server == "") server = CONFIG_DB_HOST; if (!db || db == "") db = CONFIG_DB; if (!username || username == "") username = CONFIG_DB_USER; //If we're using the nonblocking server if (nonblocking == 1) { handle = sql_nb_connect(server, db, username, pass); //log_file(SQL_LOG,"Connecting non-blocking on socket "+handle+"\n"); return handle; } else { //Blocking server foo = db_connect(server, db, username); log_file(SQL_LOG,"Connecting blocking on socket "+foo+"\n"); if (!intp(foo)) { log_file(SQL_LOG,"db_connect returned: "+foo+"\n"); return 0; } else { return foo; } } } /* mixed SQL_QUERY (int handle, string query, int nonblocking, mixed callback) * NON-BLOCKING: * Returns 0 on fail, 1 on success, or negative EE-series Socket Error codes. * See "socket_err.h" for info on negative error codes. * BLOCKING: * Returns 0 on fail, ({}) on 0 rows, string for error message * Returns array of arrays for resultset: * ({ / * sizeof() == 105 * / * ({ / * sizeof() == 7 * / * 1, * "dirk", * "Dirk the Tired", * "/domains/town/npc/dirk", * "male", * "0", * "human" * }), * ... * }) */ varargs mixed sql_query (int handle, string query, int nonblocking, mixed callback) { string *res = ({}); string *ret = ({}); mixed rows; int i; if (!handle || !query || query == "") { log_file(SQL_LOG,"No handle/query given\n"); return 0; } //Blocking doesn't do callbacks if ( functionp(callback) && nonblocking == 0 ) { log_file(SQL_LOG,"SQL_QUERY: Blocking SQL server does NOT accept callbacks. Query: "+query); return 0; } //CHAT_D->eventSendChannel("SQL Daemon","connections","Got query for socket "+handle+", query: "+query+", non-blocking: "+nonblocking,0); //If we're using the nonblocking server, send query request. if (nonblocking == 1) { return make_nb_sql_request(handle, query, callback); } else { //Blocking server. Do and return query. if (handle < 1) { log_file(SQL_LOG,"Handle < 1... Not properly connected to DB\n"); return 0; } rows = db_exec(handle, query); if( !rows ) { return ({}); } else if( stringp(rows) ) { //Error out log_file(SQL_LOG,"Rows: "+rows+"\n"); return rows; } else { for(i=1; i<=rows; i++) { res = db_fetch(handle, i); ret += ({ res }); } return ret; } } } /* Begins a new transaction * NOTE: MSQL does not have transaction logic, but MySQL DOES, * so you CAN allow commits if you disable * the AUTOCOMMIT (SET AUTOCOMMIT=0) feature. * See http://dev.mysql.com/doc/refman/5.0/en/commit.html for more. * Saving a "commit" until after you spam commands keeps * the unnecessary disk writes to a minimum, and you can * "Undo" with a rollback if something goes wrong. * * Returns 1 on success, 0 on failure */ int sql_start_transaction(int handle, int nonblocking) { if (nonblocking == 1) { //Send a 'Commit' by way of non-blocking SQL return sql_query(handle, "START TRANSACTION", 0); } else { //Send a 'Commit' by way of blocking SQL // Not sure we have an efun for this. return sql_query(handle, "START TRANSACTION", 1); } } /* Not yet implemented (maybe?) at the driver level. * * Commits the last set of transactions to the database * NOTE: MSQL does not have transaction logic, but MySQL DOES, * so you CAN allow commits if you disable * the AUTOCOMMIT (SET AUTOCOMMIT=0) feature. * See http://dev.mysql.com/doc/refman/5.0/en/commit.html for more. * Saving a "commit" until after you spam commands keeps * the unnecessary disk writes to a minimum, and you can * "Undo" with a rollback if something goes wrong. * * Returns 1 on success, 0 on failure */ varargs int sql_commit(int handle, int nonblocking) { if (nonblocking == 1) { //Send a 'Commit' by way of non-blocking SQL return make_nb_sql_request(handle, "COMMIT"); } else { //Send a 'Commit' by way of blocking SQL return db_commit(handle); } } /* Rolls back all db_exec() calls back to the last db_commit() call * for the named connection handle. * NOTE: MSQL does not support rollbacks, but MySQL CAN, if you * disable the AUTOCOMMIT (SET AUTOCOMMIT=0); * See http://dev.mysql.com/doc/refman/5.0/en/commit.html for more. * * Returns 1 on success, 0 on failure */ varargs int sql_rollback(int handle, int nonblocking) { if (nonblocking == 1) { //Send a 'Rollback' by way of non-blocking SQL return make_nb_sql_request(handle, "ROLLBACK"); } else { //Send a 'Rollback' by way of blocking SQL return db_rollback(handle); } } /* * Returns a string describing the database package's current status * May be "" if nothing connected, or like "MYSQL->1" for MYSQL * operating on handle 1. */ varargs string sql_status(int nonblocking) { if (nonblocking == 1) { //Send non-blocking SQL's status return "MYSQL->"+ sizeof(_callbacks)+" callbacks outstanding"; } else { //Send blocking SQL's status return db_status(); } } /* * Closes the database named by 'handle'. 'nonblocking' should be 1 * if we're talking to the non-blocking daemon, and 'callback' should * be one if we're talkign to the non-blocking daemon and waiting for * a callback. */ varargs int sql_close(int handle, int nonblocking, int callback) { if (!handle || handle < 0) { log_file(SQL_LOG,"SQL_CLOSE: No handle given or negative: "+handle+"\n"); return 0; } if (nonblocking == 1) { /* Call out to Close non-blocking SQL in NB_TIMEOUT seconds. * We do this because usually sql_close is called before the * data is even sent to the SQL server. */ if (callback == 1) { /* //We're going to try leaving last one open until not needed. * log_file(SQL_LOG,"Sending for close in "+NB_TIMEOUT+" seconds.\n"); * return call_out( (: sql_nb_close :), NB_TIMEOUT, handle, 1 ); */ } else { /* //We're going to try leaving last one open until not needed. * log_file(SQL_LOG,"Sending for close NOW.\n"); * return sql_nb_close(handle, 0); */ //log_file(SQL_LOG,"Leaving NB Sock "+handle+" open for next use.\n"); } } else { if ( callback == 1 ) log_file(SQL_LOG,"SQL_CLOSE: Blocking SQL Daemon does NOT do callbacks."); //Close blocking SQL log_file(SQL_LOG,"SQL_CLOSE: Closing blocking socket "+handle); return db_close(handle); } } /* varargs mixed SQL_QUICKQUERY (string query, string server, * string db, string username, string pass) * * A quick, clean-code way to insert, update or delete a * record or recordset, since insert/update/delete queries * have no resultset. * * Returns 1 on Success or 0 on Failure * * sql_quicksubmit can process multiple inserts/updates/deletes * by making the "query" an array of queries. IE: * ({ "INSERT INTO FOO WHERE..", "DELETE FROM BLAH WHERE.." }) * however, string queries will work also. IE: * "INSERT INTO FOO WHERE..." * * NB: QuickQueries ALWAYS use non-blocking code. */ varargs mixed sql_quicksubmit (mixed query, string host, string db, string username, string pass) { mixed handle; mixed ret; int i; if (!stringp(query) && !arrayp(query) || intp(query) || functionp(query)) { log_file(SQL_LOG,"SQL_QUICKSUBMIT: Bad query."); return 0; } if (!host || host == "") host = CONFIG_DB_HOST; if (!db || db == "") db = CONFIG_DB; if (!username || username == "") username = CONFIG_DB_USER; handle = sql_connect(host, db, username, pass, 1); //Connect if ( !intp(handle) || handle < 0 ) return 0; if ( arrayp(query) ) { for(i=0; i<=sizeof(query)-1; i++) { //ret should normally be 0 or ({}) for no-return queries ret = make_nb_sql_request(handle, query[i]); if ( ret != 0 && ret != ({}) ) { //Log funny results. log_file(SQL_LOG,"Query '"+query[i]+" returned something " "funny: "+sprintf("%O",ret)+"\n"); //Rollback all queries. (Maybe) //sql_rollback(handle); //FAIL. (Maybe) //sql_close(handle,1); return 0; } } } else if ( stringp(query) ) { ret = make_nb_sql_request(handle, query); } else { //WTF? sql_close(handle, 1); //Disconnect return 0; } sql_commit(handle, 1); //Not yet implemented sql_close(handle, 1); //Disconnect } /* varargs mixed SQL_QUICKCHECK (string query, string server, * string db, string username, string pass) * * A quick, clean-code way to insert, update or delete a * record or recordset, since insert/update/delete queries * have no resultset. * * Returns a value: int, string, array, etc. * * NB: QuickChecks ALWAYS use Blocking code. */ varargs mixed sql_quickcheck (mixed query, string host, string db, string username, string pass) { mixed handle; mixed ret, tmp; if ( !stringp(query) ) { log_file(SQL_LOG,"SQL_QUICKCHECK: Bad query."); return 0; } if (!host || host == "") host = CONFIG_DB_HOST; if (!db || db == "") db = CONFIG_DB; if (!username || username == "") username = CONFIG_DB_USER; handle = sql_connect(host, db, username, pass); //Connect if ( !intp(handle) || handle < 0 ) return 0; ret = sql_query(handle, query); //sql_commit(handle); sql_close(handle); //Disconnect //If we got one result... if ( arrayp(ret) && sizeof(ret) == 1 ) { //Break it out //If the result was an array of one, break it out again if ( arrayp(ret[0]) && sizeof(ret[0]) == 1 ) tmp = ret[0][0]; else //Just break once. tmp = ret[0]; ret = tmp; } return ret; } //**************EXCLUSIVELY NON-BLOCKING CODE ********************* varargs int sql_nb_connect(string host, string db, string user, string pass) { int id, handle = 0; function func; string args = ""; //If we're not using custom host... use default if (!host) host = CONFIG_DB_HOST; //If we're not using custom database field... use default if (!db) db = CONFIG_DB; //If we're not using custom user... use default if (!user) user = CONFIG_DB_USER; //If we're not using custom password... use default if (!pass) pass = CONFIG_DB_PASS; //See if the last socket is good for us. if (prevhost == host && prevdb == db && prevuser == user && prevpass == pass && prevhandle > 0) { //We can use the current handle! return prevhandle; } else if (prevhandle > 0) { //We can't use the previous handle. Kill it with fire. After its callouts. call_out( (: sql_nb_close :), NB_TIMEOUT, prevhandle, 1 ); } //Store the password as a file-wide variable until it is needed. //This is because we're going to send it later when the SQL Client asks for it specifically //instead of sending it in the command line, because command-line entries are viewable //in some cases. temppass = pass; //Args: // --no-auto-rehash: Turn off automatic rehashing. We don't need auto-complete for tables and fields. args += "--no-auto-rehash "; // --force: Continue even if we get an sql error. args += "--force "; // --ignore-spaces: Ignore space after function names. args += "--ignore-spaces "; // --no-beep: Turn off beep on error. args += "--no-beep "; // --xml: Produce XML output. args += "--xml "; // --column-names: Write column names in results. //args += "--column-names "; // --batch: Enable batch mode (enables silent mode by default) args += "--batch "; // --compress: Use compression in server/client protocol //args += "--compress "; // --unbuffered: Don't buffer the output, just dump it all at once. args += "--unbuffered "; // --quick: Do not cache each query result, print each row as it is received. //args += "--quick "; // --reconnect: Automatically try to reconnect to MySQL Server. args += "--reconnect "; // --host=name: Connect to host if (CONFIG_DB_HOST) args += "--host="+CONFIG_DB_HOST+" "; // --port=#: Set port number if (CONFIG_DB_PORT) args += "--port="+CONFIG_DB_PORT+" "; // --database=name: Set database args += "--database="+db+" "; // --user=name: User for login args += "--user="+user+" "; // --password: Ask for password -> Should ask for password instead of providing it in the command line for security reasons. However, something's funky, Asking for password will be enabled in later versions if (pass) args += "--password="+pass; //Fire up the link to the 'mysql' command! VROOOOOOOOOOM! handle = external_start( CMD_NUM, args, "read_call_back", "write_call_back", "close_call_back" ); //CHAT_D->eventSendChannel("SQL Daemon","connections","Connecting to SQL Client on Socket "+(string)handle,0); log_file(SQL_LOG,"Connecting to SQL Client on Socket "+(string)handle+" Args:"+args+"\n"); //If the socket still isn't open, something's wrong with it. Dump it and try getting a new one. if (handle < 0) { mapping temp; log_file(SQL_LOG,"Bad handle.\n"); //tell_object(find_player("shadyman"),"SQL Setup(), Bad handle! Socket: "+(string)handle); handle = -1; //remove_call_out(_callout_id); //_callout_id = call_out("setup", 20); // // Send back an error to anyone still waiting. // foreach (id, func in _callbacks) { map_delete(_callbacks, id); evaluate(func, DB_ERROR_BAD_SOCKET, socket_error(handle)); log_file(SQL_LOG,"DB Error: Bad Socket.\n"); } _requests = ([]); return -1; } else { prevhost = host; prevdb = db; prevuser = user; prevpass = pass; prevhandle = handle; return handle; } } /* setup() */ /* Check stack to make sure we're not being haxxored. * Return 1 if ok to proceed */ int valid_nb_database() { /*Put in checks here for files that you call MySQL functions from, * if they are not in /secure/ */ string prevfile; prevfile = file_name(previous_object()); //If previous object starts with /secure/save/creators or players if (!strsrch(prevfile,"/secure/save/creators/") || !strsrch(prevfile,"/secure/save/players/")) { return 1; } //Add files here to allow them to make sql requests. switch (prevfile) { case "/secure/daemon/chat": //Fallthrough case "/secure/daemon/imc2": //Fallthrough case "/secure/daemon/economy": //Fallthrough case "/lib/body": return 1; break; default : return 0; break; } return 0; } /** (Almost) Original DW Comment Block: * This is the method you call to make an sql request. You pass in the * database, user and password you wish to use to connect to the * sql sever. You also pass in the request you wish to make and the call * back function to call. *

* The call back function will be passed two arguements, they are a * 'type' and a 'data' arguement. The type will always be an integer and * it registers the success or failure of the database query. The * data will either be a string (in the case of an error) or it will be * teh returned data (in the case of request). The returned data will * be an array of mappings, the mappings contain keys of the field type * and the data value being the returned data. *

* The format of the function call is:
* void return_function(int status, mapping data) * @example * void finish_request(int type, mixed* data, object person) { * string ret; * mapping row; * * if (type == DB_SUCCESS) { * ret = ""; * foreach (row in data) { * ret += sprintf("%-15s %s\n", row["Fixer"], "" + row["bing"]); * } * } else { * ret = "Some sort of horrible error!\n"; * } * person->more_string(ret, "details"); * } * * DB_HANDLER->make_nb_sql_request("errors", CONFIG_DB_USER, "", request, * (: finish_request($1, $2, $(this_player())) :)); * @param db the database to connect to * @param user the user to use * @param pass the password to use * @param request the request to make * @param finish the call back function */ varargs int make_nb_sql_request(int handle, string request, mixed callback) { mixed *stuff; //if (!valid_nb_database()) return 0; _currentId++; if (functionp(callback)) { _callbacks[_currentId] = callback; _requests[request] = _currentId; } //If we're using new/different credentials, login with // if (db && db != CONFIG_DB || // user && user != CONFIG_DB_USER || // pass && pass != CONFIG_DB_PASS) setup(db, user, pass); //Make sure the daemon still has a socket. //if (handle == -1) sql_nb_connect(host, db, user, pass); //if (handle == -1) log_file(SQL_LOG,"Ohcrap, handle == -1\n"); //If still doesn't have a socket, fail miserably. //if (handle == -1) return 0; //CHAT_D->eventSendChannel("SQL Daemon","connections","MySQL Client sending request on socket "+(string)handle,0); //log_file(SQL_LOG,"MySQL Client sending request on socket "+(string)handle+"\n"); return socket_write(handle, request + ";\n"); } /* make_sql_request() */ /* void DEST_ME() * This will do exciting things when we dest. */ void dest_me() { int id; function func; //tell_object(find_player("shadyman"),"SQL dest_me(), Socket: "+(string)handle); foreach (id, func in _callbacks) { map_delete(_callbacks, id); catch(evaluate(func, DB_ERROR_BAD_SOCKET, "the server was dested.")); } _requests = ([]); //::dest_me(); } /* dest_me() */ /* mapping NB_QUERY_REQUSTS() * Returns the currently outstanding requests. */ mapping nb_query_requests() { return copy(_requests); } /* query_requests() */ /* int NB_ISCONNECTED() * If NON-BLOCKING SQL is connected, returns the socket number of the connection. */ /*int nb_isConnected(int handle) { return (handles(handle) > -1); }*/ /* mixed REFORMCHARACTERS(string value) * Get rid of html-ized characters in strings, and quote them. * Turns "1.111" into float 1.111 * Turns ""foo"" into string 'foo' * Turns "2342" into int 2342 */ mixed ReformCharacters(string value) { mixed temp; if (value == "NULL") return 0; temp = restore_variable(value); if (strlen(value) > 1 && temp == 0) //String! return value; else return temp; } /* mixed *PARSE_XML(string resultset) * Pares down the XML into ({ ([ row_1 : ([ field_1 : data_1 ]) ... ]), ... }) */ mixed *ParseXML(string resultset) { string *lines; mapping *rows = ({}); //temporary strings for sscanf'ing string fieldname; string fieldvalue; mapping row; row = ([]); lines = explode(resultset, "\n"); foreach (string line in lines) { line = trim(line); switch (line[0..4]) { case "%s", fieldname, fieldvalue) > -1 ) { row += ([ fieldname : ReformCharacters(fieldvalue) ]); //} else if (sscanf(line, "", fieldname ) > -1) { } else { row += ([ fieldname : 0 ]); //tempbuild += "\""+fieldname+"\":0,"; } break; case "": //New row row = ([]); break; case "eventSendChannel("SQL Daemon","connections","MySQL Client returning callback # "+(string)id,0); log_file(SQL_LOG,"MySQL Client returning callback # "+(string)id+"\n"); evaluate(_callbacks[id], errno, msg); map_delete(_callbacks, id); map_delete(_requests, id); return; } /* void DATA_IN(string msg) * Find the next full XML resultset, if possible. Otherwise, wait for more. * XML Statement Starts with: * Ends with: */ void DataIn(int handle, string msg) { int firststart, firstend; //Positions of what we want out of 'data' int statementstart, statementlen, statementend; //position and length of SQL statement returned in resultset int requestId; // string *statements; string resultset; //What we want out of 'data' string textresult; //What we get back from the XML parser. string err; mapping *restored; if (!data[handle]) data[handle] = ""; /*Find the next full XML resultset, if possible. * XML Statement Starts with: * Ends with: */ //tell_object(find_player("shadyman"),"Got: "+msg); data[handle] += msg; firststart = strsrch(data[handle],""); //Find the end of the first set. //tell_object(find_player("shadyman"),"dataIn: Got length: "+(firstend-firststart)+". Start: "+firststart+", End: "+firstend); //tell_object(find_player("shadyman"),"FirstStart: "+firststart+", FirstEnd:"+firstend); if (firstend == -1 || firststart == -1) return; //We don't have a full resultset. Wait for more. else { //Keep what we want resultset = data[handle][firststart..firstend+12]; //Put back what we don't want. if (firststart == 0) //If we started at the beginning data[handle] = data[handle][firstend+12..]; //put back what was left at the end, if any. else //if we started at the middle somewhere data[handle] = data[handle][0..firststart-1] + data[handle][firstend+13..]; //put back what's left. //tell_object(find_player("shadyman"),"dataIn: About to parse: "+resultset); //Parse resultset. restored = ParseXML(resultset); //tell_object(find_player("shadyman"),"dataIn: Parsed, and got: "+implode(values(restored[0]), ",")); //Now find the statement this resultset is answering statementstart = strsrch(resultset," // can (but doesn't have to) include xmlns:xsi.... after the statement="" if (strsrch(resultset[statementstart..],"\" xmlns") > 0) statementlen = strsrch(resultset[statementstart..],"\" xmlns") - 1; //Find the next end-element else statementlen = strsrch(resultset[statementstart..],"\">") - 1; //Find the next end-element statementend = statementstart + statementlen; //calculate the end character if (arrayp(restored) && sizeof(restored)) { //tell_object(find_player("shadyman"),"dataIn: Arrayed and exists"); //Find out if we have a requestId if (requestId = _requests[resultset[statementstart..statementend]]) { map_delete(_requests, resultset[statementstart..statementend]); //Ok, do the callback they wanted //tell_object(find_player("shadyman"),"dataIn: Calling back"); return_callback(requestId, DB_SUCCESS, restored); //} else { //tell_object(find_player("shadyman"),"dataIn: No callback"); } } } //See if there are any more full results waiting in the queue //tell_object(find_player("shadyman"),"dataIn: datalength left over: "+strlen(data)); if (strlen(data[handle]) > 1 && strsrch(data[handle]," 0 && strsrch(data[handle],"resultset>") >0) DataIn(handle,""); //Run this function again to parse it and look for more resultsets else data[handle] = ""; //_callbacks[_currentId] = finish; //_requests[_currentId] = request; return; } /* void READ_CALL_BACK (int handle, mixed msg) * Talk about sending mixed messages... * Called when data is received over the link to 'mysql'. */ void read_call_back(int handle, mixed msg) { int errno, errno2, errline; string errtext; //String message if (stringp(msg)) { log_file(SQL_LOG,"Socket "+handle+" read_call_back() sent a string.\n"); //Fix this code up later: if (msg == "Enter password: ") { //Send the stored password socket_write(handle, temppass + "\n"); tell_object(find_player("shadyman"),"Sent password '"+temppass+"' to Socket "+handle+"."); //Remove the password from memory as a security precaution temppass = ""; return; } else if (sscanf(msg, "ERROR %s", errtext)) { //CHAT_D->eventSendChannel("SQL Daemon","connections","MySQL Server: Socket "+handle+" returned Error: "+errtext,0); log_file(SQL_LOG,"MySQL Server: Socket "+handle+" returned Error: "+errtext+"\n"); //tell_object(find_player("shadyman"),"Socket "+handle+" read_call_back() ERROR: "+errtext); //error handling here return; } else { DataIn(handle,msg); return; } } else { //tell_object(find_player("shadyman"),"Socket "+handle+" read_call_back() did NOT send a string."); //error handling here return; } } /* read_call_back() */ /* int SQL_NB_CLOSE(int handle, int calledout) * Call this to close socket 'handle' * If it's being called through a call_out, calledout = 1 */ varargs int sql_nb_close(int handle, int calledout) { /* Remove the callout for this function closest to completion * AKA, the one that called this function. */ if (calledout == 1) { remove_call_out( find_call_out( "sql_nb_close" ) ); log_file(SQL_LOG,"MySQL Server: Removed callout for socket "+handle+"\n"); } //CHAT_D->eventSendChannel("SQL Daemon","connections","MySQL Client closing socket "+(string)handle,0); log_file(SQL_LOG,"MySQL Client closing socket "+(string)handle+"\n"); return socket_close(handle); } /* void CLOSE_CALL_BACK (int handle) * Called when the link to 'mysql' is closed. */ void close_call_back(int handle) { //CHAT_D->eventSendChannel("SQL Daemon","connections","MySQL Client closed the connection on socket "+(string)handle,0); //remove_call_out(_callout_id); //_callout_id = call_out("setup", 10) }