#include "TclNamedReference.h" #include "../shared/DatabaseForThread.h" #include "../shared/TclUtil.h" #include "CandleClient.h" #include "TclDatabaseObjectWrapper.h" namespace TclDatabaseObjectWrapper { // This interface is used to store a database as a TCL object. class Database : public HasNamedReference { protected: // These objects are not thread safe. Tell the NamedReference mechanism // not to find() one of these in the wrong thread. Database() : HasNamedReference(true) { } public: // The whole point of this object is to hold a pointer to a database. virtual DatabaseWithRetry &getDatabase() =0; }; // This contains a DatabaseWithRetry object. The DatabaseWithRetry is // created when this object is created, and it is destroyed when this // object is destroyed. class NewDB : public Database { private: DatabaseWithRetry _database; public: NewDB(std::string const &databaseName, std::string const &debugName) : _database(databaseName, debugName) { } NewDB(bool readOnly, std::string const &debugName) : _database(readOnly, debugName) { } virtual DatabaseWithRetry &getDatabase() { return _database; } }; // This contains a reference to a DatabaseWithRetry object. Someone else // has to create that object. Nothing interesting happens when this object // is destroyed. class Wrapper : public Database { private: DatabaseWithRetry &_database; public: // Something that already exists. We never delete it. Assume it never // goes away on its own. Wrapper(DatabaseWithRetry &database) : _database(database) { } virtual DatabaseWithRetry &getDatabase() { return _database; } }; // A wrapper around MysqlResultRef to create a NamedReference. class Result : public HasNamedReference { private: MysqlResultRef _value; public: Result(MysqlResultRef value) : _value(value) { } MysqlResult *get() { return &*_value; } }; ///////////////////////////////////////////////////////////////////// // Global ///////////////////////////////////////////////////////////////////// NamedReference createByName(std::string const &databaseName, std::string const &debugName) { return new NewDB(databaseName, debugName); } // True on success. If there is an error, leave the error message in the // interpreter. Some errors might not leave any error message. bool addDatabase(std::string const &databaseServerName, std::string const &varName, Tcl_Interp *interp) { if (!DatabaseWithRetry::probablyWorks(databaseServerName)) return false; DatabaseWithRetry *const database = DatabaseForThread(databaseServerName); Tcl_Obj *result = Tcl_ObjSetVar2(interp, makeTclString(varName), NULL, createTclObject(new Wrapper(*database)), TCL_LEAVE_ERR_MSG); return result; } void addStandardDatabases(Tcl_Interp *interp) { addDatabase(DatabaseWithRetry::MASTER, "ti::master_db", interp); addDatabase(DatabaseWithRetry::SLAVE, "ti::slave_db", interp); addDatabase(DatabaseWithRetry::CANDLES, "ti::candles_db", interp); addDatabase(DatabaseWithRetry::PAPER_MASTER, "ti::paper_db", interp); } int getServerNameCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "server"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Database *database = reference.as< Database >(); if (!database) { reportNotApplicableError(interp, reference, "database connection"); return TCL_ERROR; } std::string name = database->getDatabase().getDatabaseServerName(); Tcl_SetObjResult(interp, makeTclString(name)); return TCL_OK; } int queryCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "server ?which|none? ?sql …?"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Database *database = reference.as< Database >(); if (!database) { reportNotApplicableError(interp, reference, "database connection"); return TCL_ERROR; } bool returnAll = true; int returnWhich; if (objc > 2) { if (getString(objv[2]) == "none") { returnAll = false; returnWhich = -1; } else if (Tcl_GetIntFromObj(NULL, objv[2], &returnWhich) == TCL_OK) { returnAll = false; if (returnWhich < 0) { // TCL normally uses "end-2" not -3, to mean 3 from the end. I don't // know if there are standard routines to parse that. // -1 means the last one, -2 means second to the last. const int sqlStatementCount = objc - 3; returnWhich = sqlStatementCount + returnWhich; } } } std::vector< std::string > sql; for (int nextSql = returnAll?2:3; nextSql < objc; nextSql++) sql.push_back(getString(objv[nextSql])); std::vector< MysqlResultRef > allResults; if (!sql.empty()) allResults = database->getDatabase().tryAllUntilSuccess(sql.begin(), sql.end()); if (returnAll) { Tcl_Obj *result = Tcl_NewObj(); for (auto it = allResults.cbegin(); it != allResults.cend(); it++) Tcl_ListObjAppendElement(NULL, result, createTclObject(new Result(*it))); Tcl_SetObjResult(interp, result); } else { // Return a single result. // If the index for the result is out of bounds, return nothing. if ((returnWhich >= 0) && (returnWhich < (int)allResults.size())) Tcl_SetObjResult(interp, createTclObject(new Result(allResults[returnWhich]))); } return TCL_OK; } int mysqlEscapeStringCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "string"); return TCL_ERROR; } Tcl_SetObjResult(interp, makeTclString(mysqlEscapeString(getString(objv[1])))); return TCL_OK; } int numRowsCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "result"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(result->get()->numRows())); return TCL_OK; } int rowIsValidCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "result"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result->get()->rowIsValid())); return TCL_OK; } int nextRowCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "result"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } result->get()->nextRow(); return TCL_OK; } int fieldIsEmptyCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "result name"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } const std::string name = getString(objv[2]); Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result->get()->fieldIsEmpty(name))); return TCL_OK; } int nFieldIsEmptyCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "result index"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } int index; if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) return TCL_ERROR; Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result->get()->fieldIsEmpty(index))); return TCL_OK; } int getFieldCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 1, objv, "result name ?default?"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } const std::string name = getString(objv[2]); std::string defaultValue; if (objc >= 4) defaultValue = getString(objv[3]); Tcl_SetObjResult(interp, makeTclString(result->get() ->getStringField(name, defaultValue))); return TCL_OK; } int nGetFieldCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if ((objc < 3) || (objc > 4)) { Tcl_WrongNumArgs(interp, 1, objv, "result index ?default?"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } int index; if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) return TCL_ERROR; std::string defaultValue; if (objc >= 4) defaultValue = getString(objv[3]); Tcl_SetObjResult(interp, makeTclString(result->get() ->getStringField(index, defaultValue))); return TCL_OK; } int getRowAsDictCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "result"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } Tcl_Obj *dict = Tcl_NewObj(); std::vector< std::string > names; result->get()->getColumnNames(names); int index = 0; for (std::vector< std::string >::const_iterator it = names.begin(); it != names.end(); it++, index++) if (!result->get()->fieldIsEmpty(index)) { Tcl_ListObjAppendElement(interp, dict, makeTclString(*it)); Tcl_ListObjAppendElement(interp, dict, makeTclString(result->get() ->getStringField(index))); } Tcl_SetObjResult(interp, dict); return TCL_OK; } int getRowAsListCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "result"); return TCL_ERROR; } NamedReference reference = extractNamedReference(objv[1], interp); Result *result = reference.as< Result >(); if (!result) { reportNotApplicableError(interp, reference, "database result"); return TCL_ERROR; } Tcl_Obj *list = Tcl_NewObj(); const int max = result->get()->getFieldCount(); for (int index = 0; index < max; index++) Tcl_ListObjAppendElement(interp, list, makeTclString(result->get() ->getStringField(index))); Tcl_SetObjResult(interp, list); return TCL_OK; } void addStandardCommands(Tcl_Interp *interp) { Tcl_CreateObjCommand(interp, "ti::get_server_name", getServerNameCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::query", queryCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::mysql_escape_string", mysqlEscapeStringCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::num_rows", numRowsCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::row_is_valid", rowIsValidCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::next_row", nextRowCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::field_is_empty", fieldIsEmptyCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::n_field_is_empty", nFieldIsEmptyCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::get_field", getFieldCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::n_get_field", nGetFieldCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::get_row_as_dict", getRowAsDictCmd, NULL, NULL); Tcl_CreateObjCommand(interp, "ti::get_row_as_list", getRowAsListCmd, NULL, NULL); } }