// This file is part of BOINC. // http://boinc.berkeley.edu // Copyright (C) 2019 University of California // // BOINC is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License // as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // BOINC is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . #include "config.h" #include #include #include #include #include "error_numbers.h" #include "str_util.h" #include "str_replace.h" #include "db_base.h" #ifdef _USING_FCGI_ #include "fcgi_stdio.h" #include "sched_msgs.h" #endif bool g_print_queries = false; DB_CONN::DB_CONN() { mysql = 0; } int DB_CONN::open( char* db_name, char* db_host, char* db_user, char* dbpassword ) { mysql = mysql_init(0); if (!mysql) return ERR_DB_CANT_INIT; // MySQL's support for the reconnect option has changed over time: // see http://dev.mysql.com/doc/refman/5.0/en/mysql-options.html // and http://dev.mysql.com/doc/refman/5.1/en/mysql-options.html // // v < 5.0.13: not supported // 5.0.13 <= v < 5.0.19: set option after real_connect() // 5.0.19 < v < 5.1: set option before real_connect(); // 5.1.0 <= v < 5.1.6: set option after real_connect() // 5.1.6 <= v: set option before real_connect int v = MYSQL_VERSION_ID; bool set_opt_before = false, set_opt_after = false; if (v < 50013 ) { } else if (v < 50019) { set_opt_after = true; } else if (v < 50100) { set_opt_before = true; } else if (v < 50106) { set_opt_after = true; } else { set_opt_before = true; } if (set_opt_before) { bool mbReconnect = 1; mysql_options(mysql, MYSQL_OPT_RECONNECT, &mbReconnect); } // parse hostname:port // char host[256]; int port = 0; safe_strcpy(host, db_host); char* p = strchr(host, ':'); if (p) { *p = 0; port = atoi(p+1); } // CLIENT_FOUND_ROWS means that the # of affected rows for an update // is the # matched by the where, rather than the # actually changed // MYSQL* mysql2 = mysql_real_connect( mysql, host, db_user, dbpassword, db_name, port, 0, CLIENT_FOUND_ROWS ); if (mysql2 == 0) { fprintf(stderr, "mysql_real_connect(): %d: %s\n", mysql_errno(mysql), mysql_error(mysql)); return ERR_DB_CANT_CONNECT; } mysql = mysql2; if (set_opt_after) { bool mbReconnect = 1; mysql_options(mysql, MYSQL_OPT_RECONNECT, &mbReconnect); } return 0; } void DB_CONN::close() { if (mysql) mysql_close(mysql); } int DB_CONN::set_isolation_level(ISOLATION_LEVEL level) { const char* level_str; char query[256]; switch(level) { case READ_UNCOMMITTED: level_str = "READ UNCOMMITTED"; break; case READ_COMMITTED: level_str = "READ COMMITTED"; break; case REPEATABLE_READ: level_str = "REPEATABLE READ"; break; case SERIALIZABLE: level_str = "SERIALIZABLE"; break; default: return -1; } sprintf(query, "SET SESSION TRANSACTION ISOLATION LEVEL %s", level_str); return do_query(query); } int DB_CONN::do_query(const char* p) { int retval; if (g_print_queries) { #ifdef _USING_FCGI_ log_messages.printf(MSG_NORMAL, "query: %s\n", p); #else fprintf(stderr, "query: %s\n", p); #endif } retval = mysql_query(mysql, p); if (retval) { fprintf(stderr, "Database error: %s\nquery=%s\n", error_string(), p); } return retval; } // returns the number of rows matched by an update query // int DB_CONN::affected_rows() { unsigned long x = (unsigned long)mysql_affected_rows(mysql); //fprintf(stderr, "x: %lu i: %d\n", x, (int)x); return (int)x; } DB_ID_TYPE DB_CONN::insert_id() { int retval; MYSQL_ROW row; MYSQL_RES* rp; retval = do_query("select LAST_INSERT_ID()"); if (retval) return retval; rp = mysql_store_result(mysql); row = mysql_fetch_row(rp); DB_ID_TYPE x = atol(row[0]); mysql_free_result(rp); return x; } void DB_CONN::print_error(const char* p) { fprintf(stderr, "%s: Database error: %s\n", p, error_string()); } const char* DB_CONN::error_string() { return mysql?mysql_error(mysql):"Not connected"; } int DB_CONN::start_transaction() { return do_query("START TRANSACTION"); } int DB_CONN::commit_transaction() { return do_query("COMMIT"); } int DB_CONN::rollback_transaction() { return do_query("ROLLBACK"); } int DB_CONN::ping() { int retval = mysql_ping(mysql); if (retval) return ERR_DB_CANT_CONNECT; return 0; } DB_BASE::DB_BASE(const char *tn, DB_CONN* p) : db(p), table_name(tn) { } DB_ID_TYPE DB_BASE::get_id() { return 0;} void DB_BASE::db_print(char*) {} void DB_BASE::db_parse(MYSQL_ROW&) {} int DB_BASE::insert() { char vals[MAX_QUERY_LEN*2], query[MAX_QUERY_LEN*2]; db_print(vals); sprintf(query, "insert into %s set %s", table_name, vals); return db->do_query(query); } int DB_BASE::insert_batch(std::string& values) { std::string query; query = "insert into " + std::string(table_name) + " values " + values; return db->do_query(query.c_str()); } int DB_BASE::affected_rows() { return db->affected_rows(); } //////////// FUNCTIONS FOR TABLES THAT HAVE AN ID FIELD /////// int DB_BASE::lookup_id(DB_ID_TYPE id) { char query[MAX_QUERY_LEN]; int retval; MYSQL_ROW row; MYSQL_RES* rp; sprintf(query, "select * from %s where id=%lu", table_name, id); retval = db->do_query(query); if (retval) return retval; rp = mysql_store_result(db->mysql); if (!rp) return -1; row = mysql_fetch_row(rp); if (row) db_parse(row); mysql_free_result(rp); if (row == 0) return ERR_DB_NOT_FOUND; // don't bother checking for uniqueness here return 0; } // update an entire record // int DB_BASE::update() { char vals[MAX_QUERY_LEN], query[MAX_QUERY_LEN]; db_print(vals); sprintf(query, "update %s set %s where id=%lu", table_name, vals, get_id()); int retval = db->do_query(query); if (retval) return retval; if (db->affected_rows() != 1) return ERR_DB_NOT_FOUND; return 0; } // update one or more fields // "clause" is something like "foo=5, blah='xxx'" or "foo=foo+5" // int DB_BASE::update_field(const char* clause, const char* where_clause) { char query[MAX_QUERY_LEN]; if (where_clause) { sprintf(query, "update %s set %s where id=%lu and %s", table_name, clause, get_id(), where_clause ); } else { sprintf(query, "update %s set %s where id=%lu", table_name, clause, get_id() ); } return db->do_query(query); } // delete record // int DB_BASE::delete_from_db() { char query[MAX_QUERY_LEN]; sprintf(query, "delete from %s where id=%lu", table_name, get_id()); return db->do_query(query); } int DB_BASE::delete_from_db_multi(const char* where_clause) { char query[MAX_QUERY_LEN]; sprintf(query, "delete from %s where %s", table_name, where_clause); return db->do_query(query); } int DB_BASE::get_field_ints(const char* fields, int nfields, int* vals) { char query[MAX_QUERY_LEN]; int retval; MYSQL_ROW row; MYSQL_RES* rp; sprintf(query, "select %s from %s where id=%lu", fields, table_name, get_id() ); retval = db->do_query(query); if (retval) return retval; rp = mysql_store_result(db->mysql); if (!rp) return -1; row = mysql_fetch_row(rp); if (row) { for (int i=0; ido_query(query); if (retval) return retval; rp = mysql_store_result(db->mysql); if (!rp) return -1; row = mysql_fetch_row(rp); if (row && row[0]) { strlcpy(buf, row[0], buflen); } mysql_free_result(rp); if (row == 0) return ERR_DB_NOT_FOUND; return 0; } int DB_BASE::max_id(DB_ID_TYPE& n, const char* clause) { char query[MAX_QUERY_LEN]; sprintf(query, "select max(id) from %s %s", table_name, clause); return get_long(query, n); } /////////////// FUNCTIONS THAT DON'T REQUIRE AN ID FIELD /////////////// int DB_BASE::lookup(const char* clause) { char query[MAX_QUERY_LEN]; int retval; MYSQL_ROW row; MYSQL_RES* rp; sprintf(query, "select * from %s %s", table_name, clause); retval = db->do_query(query); if (retval) return retval; rp = mysql_store_result(db->mysql); if (!rp) return -1; row = mysql_fetch_row(rp); if (row) db_parse(row); mysql_free_result(rp); if (row == 0) return ERR_DB_NOT_FOUND; return 0; } int DB_BASE::update_fields_noid(const char* set_clause, const char* where_clause) { char query[MAX_QUERY_LEN]; sprintf(query, "update %s set %s where %s", table_name, set_clause, where_clause ); int retval = db->do_query(query); if (retval) return retval; return 0; } // Note: AFAIK, if one enumeration is active you can't do another one // int DB_BASE::enumerate(const char* clause, bool use_use_result) { int x; char query[MAX_QUERY_LEN]; MYSQL_ROW row; if (!cursor.active) { cursor.active = true; sprintf(query, "select * from %s %s", table_name, clause); x = db->do_query(query); if (x) return mysql_errno(db->mysql); // if you use mysql_use_result() here, // any other transactions will fail // if (use_use_result) { cursor.rp = mysql_use_result(db->mysql); } else { cursor.rp = mysql_store_result(db->mysql); } if (!cursor.rp) return mysql_errno(db->mysql); } row = mysql_fetch_row(cursor.rp); if (!row) { mysql_free_result(cursor.rp); cursor.active = false; x = mysql_errno(db->mysql); if (x) return x; return ERR_DB_NOT_FOUND; } else { db_parse(row); } return 0; } // call this to end an enumeration before reaching end // int DB_BASE::end_enumerate() { if (cursor.active) { mysql_free_result(cursor.rp); cursor.active = false; } return 0; } int DB_BASE::get_long(const char* query, long& n) { int retval; MYSQL_ROW row; MYSQL_RES* resp; retval = db->do_query(query); if (retval) return retval; resp = mysql_store_result(db->mysql); if (!resp) return ERR_DB_NOT_FOUND; row = mysql_fetch_row(resp); if (!row || !row[0]) { retval = ERR_DB_NOT_FOUND; } else { n = atol(row[0]); } mysql_free_result(resp); return retval; } int DB_CONN::get_double(const char* query, double& x) { int retval; MYSQL_ROW row; MYSQL_RES* resp; retval = do_query(query); if (retval) return retval; resp = mysql_store_result(mysql); if (!resp) return ERR_DB_NOT_FOUND; row = mysql_fetch_row(resp); if (!row || !row[0]) { retval = ERR_DB_NOT_FOUND; } else { x = atof(row[0]); } mysql_free_result(resp); return retval; } int DB_BASE::count(long& n, const char* clause) { char query[MAX_QUERY_LEN]; sprintf(query, "select count(*) from %s %s", table_name, clause); return get_long(query, n); } int DB_BASE::sum(double& x, const char* field, const char* clause) { char query[MAX_QUERY_LEN]; sprintf(query, "select sum(%s) from %s %s", field, table_name, clause); return db->get_double(query, x); } DB_BASE_SPECIAL::DB_BASE_SPECIAL(DB_CONN* p) : db(p) { } // convert a string into a form that allows it to be used // in SQL queries delimited by single quotes: // replace ' with \', '\' with '\\' // void escape_string(char* field, int len) { char buf[MAX_QUERY_LEN]; char* q = buf, *p = field; if (len > MAX_QUERY_LEN) { len = MAX_QUERY_LEN; } // Make sure that final result won't overflow field[]. // while (*p && q < buf+len-3) { if (*p == '\'') { // this does ' to \' transformation // *q++ = '\\'; *q++ = '\''; } else if (*p == '\\') { // this does \ to \\ transformation // *q++ = '\\'; *q++ = '\\'; } else { // this handles all other characters // *q++ = *p; } p++; } *q = 0; strlcpy(field, buf, len); } // undo the above process // (len not used because this doesn't expand the string) // void unescape_string(char* p, int /*len*/) { char* q = p; while (*p) { if (*p == '\\') { p++; if (!*p) break; // SHOULD NEVER HAPPEN! } *q++ = *p++; } *q='\0'; } // replace _ with \\_, % with \\% // void escape_mysql_like_pattern(const char* in, char* out) { while (*in) { if (*in == '_' || *in == '%') { *out++ = '\\'; *out++ = '\\'; } *out++ = *in++; } }