#include "../shared/ThreadClass.h" #include "../shared/TclUtil.h" #include "ExecutionContext.h" #include "TclDatabaseWrapper.h" TclDatabaseWrapper::TclDatabaseWrapper(std::string const &serverName, std::string const &debugName) : _database(serverName, debugName) { } int TclDatabaseWrapper::databaseCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return ((TclDatabaseWrapper *)clientData) ->databaseCmd(interp, objc, objv, 1); } int TclDatabaseWrapper::databaseCmd(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int offset) { if (objc < 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "subcommand ?argument ...?"); return TCL_ERROR; } static char const *subcommands[] = { "get_server_name", "query", "mquery", "escape", NULL }; int subcommand; if (Tcl_GetIndexFromObj(interp, objv[offset], subcommands, "subcommand", 0, &subcommand) != TCL_OK) return TCL_ERROR; switch (subcommand) { case 0: if (objc != 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "get_server_name"); return TCL_ERROR; } Tcl_SetObjResult(interp, makeTclString(_database.getDatabaseServerName())); return TCL_OK; case 1: { if ((objc < 2 + offset) || (objc > 3 + offset)) { Tcl_WrongNumArgs(interp, offset, objv, "query sql ?result_command_name?"); return TCL_ERROR; } const std::string sql = getString(objv[offset + 1]); MysqlResultRef result = _database.tryQueryUntilSuccess(sql); if (objc >= 3 + offset) { std::string resultCommandName = getString(objv[offset + 2]); createResultCmd(interp, resultCommandName, result); Tcl_SetObjResult(interp, makeTclString(resultCommandName)); } return TCL_OK; } case 2: { if (objc < 2 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "mquery sql_list"); return TCL_ERROR; } int inputCount; Tcl_Obj *const *nextInput; if (objc == 2 + offset) { // Only one argument. Treat it as a list of queries and names Tcl_Obj **itemsInList; int result = Tcl_ListObjGetElements(interp, objv[offset + 1], &inputCount, &itemsInList); if (result != TCL_OK) return TCL_ERROR; nextInput = itemsInList; // Note: If the list is empty, nextInput will be set to null. // So you can't just iterate over the list, looking for null, like // you would with argv in main(). } else { // More than one argument. // Each of these arguments is a query or a name. inputCount = objc - offset - 1; nextInput = objv + offset + 1; } std::vector< std::string > sql; std::vector< std::string > command; while (true) { // Iterate once for each sql / command pair. if (!inputCount) break; sql.push_back(getString(*nextInput)); inputCount--; nextInput++; if (!inputCount) { // We had an odd number of entries. command.push_back(""); break; } command.push_back(getString(*nextInput)); inputCount--; nextInput++; } std::vector< MysqlResultRef > results = _database.tryAllUntilSuccess(sql.begin(), sql.end()); assert(sql.size() == command.size()); assert(sql.size() == results.size()); Tcl_Obj *resultList = Tcl_NewObj(); // Return a list of function names. for (size_t i = 0; i < results.size(); i++) { std::string currentCommand = command[i]; if (!currentCommand.empty()) // createResultCmd can change the value of currentCommand. createResultCmd(interp, currentCommand, results[i]); int result = Tcl_ListObjAppendElement(NULL, resultList, makeTclString(currentCommand)); assert(result == TCL_OK); } Tcl_SetObjResult(interp, resultList); return TCL_OK; } case 3: if (objc != 2 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "escape string"); return TCL_ERROR; } Tcl_SetObjResult(interp, makeTclString(mysqlEscapeString(getString(objv[offset+1])))); return TCL_OK; } return TCL_ERROR; } void TclDatabaseWrapper::deleteDatabaseCmd(ClientData clientData) { delete (TclDatabaseWrapper *)clientData; } int TclDatabaseWrapper::resultCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { return resultCmd(*(MysqlResultRef *)clientData, interp, objc, objv, 1); } int TclDatabaseWrapper::resultCmd(MysqlResultRef result, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int offset) { if (objc < 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "subcommand ?argument ...?"); return TCL_ERROR; } static char const *subcommands[] = { "num_rows", "row_is_valid", "next_row", "field_is_empty", "n_field_is_empty", "get_field", "n_get_field", "get_dict", "get_list", NULL }; int subcommand; if (Tcl_GetIndexFromObj(interp, objv[offset], subcommands, "subcommand", 0, &subcommand) != TCL_OK) return TCL_ERROR; switch (subcommand) { case 0: if (objc != 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "num_rows"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(result->numRows())); return TCL_OK; case 1: if (objc != 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "row_is_valid"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result->rowIsValid())); return TCL_OK; case 2: if (objc != 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "next_row"); return TCL_ERROR; } result->nextRow(); return TCL_OK; case 3: { if (objc != 2 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "field_is_empty name"); return TCL_ERROR; } const std::string name = getString(objv[offset + 1]); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result->fieldIsEmpty(name))); return TCL_OK; } case 4: { if (objc != 2 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "n_field_is_empty index"); return TCL_ERROR; } int index; if (Tcl_GetIntFromObj(interp, objv[1 + offset], &index) != TCL_OK) return TCL_ERROR; Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result->fieldIsEmpty(index))); return TCL_OK; } case 5: { if ((objc < 2 + offset) || (objc > 3 + offset)) { Tcl_WrongNumArgs(interp, offset, objv, "get_field name ?default?"); return TCL_ERROR; } const std::string name = getString(objv[offset + 1]); std::string defaultValue; if (objc >= 3 + offset) defaultValue = getString(objv[offset + 2]); Tcl_SetObjResult(interp, makeTclString(result->getStringField(name, defaultValue))); return TCL_OK; } case 6: { if ((objc < 2 + offset) || (objc > 3 + offset)) { Tcl_WrongNumArgs(interp, offset, objv, "n_get_field index ?default?"); return TCL_ERROR; } int index; if (Tcl_GetIntFromObj(interp, objv[1 + offset], &index) != TCL_OK) return TCL_ERROR; std::string defaultValue; if (objc >= 3 + offset) defaultValue = getString(objv[offset + 2]); Tcl_SetObjResult(interp, makeTclString(result->getStringField(index, defaultValue))); return TCL_OK; } case 7: { // This is mostly for debugging and interactive use. get_field and // n_get_field are usually more convenient because they include a // default value. // This is a lot like php's mysql_get_assoc. Since TCL doesn't have // a null value, we just skip any fields that are null. if (objc != 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "get_dict"); return TCL_ERROR; } Tcl_Obj *dict = Tcl_NewObj(); std::vector< std::string > names; result->getColumnNames(names); int index = 0; for (std::vector< std::string >::const_iterator it = names.begin(); it != names.end(); it++, index++) if (!result->fieldIsEmpty(index)) { Tcl_ListObjAppendElement(interp, dict, makeTclString(*it)); Tcl_ListObjAppendElement(interp, dict, makeTclString(result->getStringField(index))); } Tcl_SetObjResult(interp, dict); return TCL_OK; } case 8: { // This is mostly for debugging and interactive use. // This will return a list of results. The length will always match // the number of items in the request. Nulls will be converted to // the empty string. if (objc != 1 + offset) { Tcl_WrongNumArgs(interp, offset, objv, "get_list"); return TCL_ERROR; } Tcl_Obj *list = Tcl_NewObj(); const int max = result->getFieldCount(); for (int index = 0; index < max; index++) Tcl_ListObjAppendElement(interp, list, makeTclString(result->getStringField(index))); Tcl_SetObjResult(interp, list); return TCL_OK; } } return TCL_ERROR; } void TclDatabaseWrapper::deleteResultCmd(ClientData clientData) { delete (MysqlResultRef *)clientData; } Tcl_Command TclDatabaseWrapper::createDatabaseCmd(Tcl_Interp *interp, std::string &commandName, std::string const &serverName, std::string debugName) { if (debugName.empty()) { debugName = commandName; if (ThreadClass *thread = ThreadClass::findThread()) { debugName += " ("; debugName += thread->getName(); debugName += ')'; } } commandName = ExecutionContext::getAbsoluteName(interp, commandName); TclDatabaseWrapper *info = new TclDatabaseWrapper(serverName, debugName); return Tcl_CreateObjCommand(interp, commandName.c_str(), databaseCmd, (ClientData)info, deleteDatabaseCmd); } Tcl_Command TclDatabaseWrapper::createResultCmd(Tcl_Interp *interp, std::string &name, MysqlResultRef result) { name = ExecutionContext::getAbsoluteName(interp, name); MysqlResultRef *info = new MysqlResultRef(result); return Tcl_CreateObjCommand(interp, name.c_str(), resultCmd, (ClientData)info, deleteResultCmd); }