/*********************************************************************** connection.cpp - Implements the Connection class. Copyright (c) 1998 by Kevin Atkinson, (c) 1999, 2000 and 2001 by MySQL AB, and (c) 2004-2006 by Educational Technology Resources, Inc. Others may also hold copyrights on code in this file. See the CREDITS file in the top directory of the distribution for details. This file is part of MySQL++. MySQL++ 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 2.1 of the License, or (at your option) any later version. MySQL++ 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 MySQL++; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ***********************************************************************/ #define MYSQLPP_NOT_HEADER #include "common.h" #include "connection.h" #include "query.h" #include "result.h" // An argument was added to mysql_shutdown() in MySQL 4.1.3 and 5.0.1. #if ((MYSQL_VERSION_ID >= 40103) && (MYSQL_VERSION_ID <= 49999)) || (MYSQL_VERSION_ID >= 50001) # define SHUTDOWN_ARG ,SHUTDOWN_DEFAULT #else # define SHUTDOWN_ARG #endif #define NELEMS(a) (sizeof(a) / sizeof(a[0])) using namespace std; namespace mysqlpp { /// \brief Sets a variable to a given value temporarily. /// /// Saves existing value, sets new value, and restores old value when /// the object is destroyed. Used to set a flag in an exception-safe /// manner. template class scoped_var_set { public: /// \brief Create object, saving old value, setting new value scoped_var_set(T& var, T new_value) : var_(var) { old_value_ = var_; var_ = new_value; } /// \brief Destroy object, restoring old value ~scoped_var_set() { var_ = old_value_; } private: T& var_; T old_value_; }; // Initialize table of legal option argument types. Connection::OptionArgType Connection::legal_opt_arg_types_[Connection::opt_COUNT] = { Connection::opt_type_integer, // opt_connect_timeout Connection::opt_type_none, // opt_compress Connection::opt_type_none, // opt_named_pipe Connection::opt_type_string, // opt_init_command Connection::opt_type_string, // opt_read_default_file Connection::opt_type_string, // opt_read_default_group Connection::opt_type_string, // opt_set_charset_dir Connection::opt_type_string, // opt_set_charset_name Connection::opt_type_integer, // opt_local_infile Connection::opt_type_integer, // opt_protocol Connection::opt_type_string, // opt_shared_memory_base_name Connection::opt_type_integer, // opt_read_timeout Connection::opt_type_integer, // opt_write_timeout Connection::opt_type_none, // opt_use_result Connection::opt_type_none, // opt_use_remote_connection Connection::opt_type_none, // opt_use_embedded_connection Connection::opt_type_none, // opt_guess_connection Connection::opt_type_string, // opt_set_client_ip Connection::opt_type_boolean, // opt_secure_auth Connection::opt_type_boolean, // opt_multi_statements Connection::opt_type_boolean, // opt_report_data_truncation Connection::opt_type_boolean, // opt_reconnect }; Connection::Connection(bool te) : OptionalExceptions(te), Lockable(false), is_connected_(false), connecting_(false), success_(false) { mysql_init(&mysql_); } Connection::Connection(const char* db, const char* host, const char* user, const char* passwd, uint port, my_bool compress, unsigned int connect_timeout, cchar* socket_name, unsigned int client_flag) : OptionalExceptions(), Lockable(false), connecting_(false) { mysql_init(&mysql_); if (connect(db, host, user, passwd, port, compress, connect_timeout, socket_name, client_flag)) { unlock(); success_ = is_connected_ = true; } else { unlock(); success_ = is_connected_ = false; if (throw_exceptions()) { throw ConnectionFailed(error()); } } } Connection::Connection(const Connection& other) : OptionalExceptions(), Lockable(false), is_connected_(false) { copy(other); } Connection::~Connection() { disconnect(); } Connection& Connection::operator=(const Connection& rhs) { copy(rhs); return *this; } bool Connection::connect(cchar* db, cchar* host, cchar* user, cchar* passwd, uint port, my_bool compress, unsigned int connect_timeout, cchar* socket_name, unsigned int client_flag) { lock(); // Drop previous connection, if any if (connected()) { disconnect(); } // Set defaults for certain connection options. User can override // these by calling set_option() before connect(). set_option_default(opt_read_default_file, "my"); set_option_default(opt_connect_timeout, connect_timeout); if (compress) { set_option_default(opt_compress); } #if MYSQL_VERSION_ID >= 40101 // Check to see if user turned on multi-statements before // establishing the connection. This one we handle specially, by // setting a flag during connection establishment. if (option_set(opt_multi_statements)) { client_flag |= CLIENT_MULTI_STATEMENTS; } #endif // Establish connection scoped_var_set sb(connecting_, true); if (mysql_real_connect(&mysql_, host, user, passwd, db, port, socket_name, client_flag)) { unlock(); success_ = is_connected_ = true; if (db && db[0]) { // Also attach to given database success_ = select_db(db); } } else { unlock(); success_ = is_connected_ = false; if (throw_exceptions()) { throw ConnectionFailed(error()); } } return success_; } bool Connection::connect(const MYSQL& mysql) { return connect(mysql.db, mysql.host, mysql.user, mysql.passwd, mysql.port, mysql.options.compress, mysql.options.connect_timeout, mysql.unix_socket, mysql.client_flag); } void Connection::copy(const Connection& other) { if (connected()) { disconnect(); } mysql_init(&mysql_); set_exceptions(other.throw_exceptions()); if (other.connected()) { // Try to reconnect to server using same parameters connect(other.mysql_); } else { is_connected_ = false; connecting_ = false; success_ = false; } } void Connection::disconnect() { mysql_close(&mysql_); is_connected_ = false; } bool Connection::create_db(const std::string& db) { Query q(this, throw_exceptions()); return q.exec("CREATE DATABASE " + db); } bool Connection::drop_db(const std::string& db) { Query q(this, throw_exceptions()); return q.exec("DROP DATABASE " + db); } bool Connection::select_db(const char *db) { if (connected()) { bool suc = !(mysql_select_db(&mysql_, db)); if (throw_exceptions() && !suc) { throw DBSelectionFailed(error()); } else { return suc; } } else { if (throw_exceptions()) { throw DBSelectionFailed("MySQL++ connection not established"); } else { return false; } } } bool Connection::reload() { if (connected()) { bool suc = !mysql_reload(&mysql_); if (throw_exceptions() && !suc) { // Reloading grant tables through this API isn't precisely a // query, but it's acceptable to signal errors with BadQuery // because the new mechanism is the FLUSH PRIVILEGES query. // A program won't have to change when doing it the new way. throw BadQuery(error()); } else { return suc; } } else { if (throw_exceptions()) { throw BadQuery("MySQL++ connection not established"); } else { return false; } } } bool Connection::shutdown() { if (connected()) { bool suc = !(mysql_shutdown(&mysql_ SHUTDOWN_ARG)); if (throw_exceptions() && !suc) { throw ConnectionFailed(error()); } else { return suc; } } else { if (throw_exceptions()) { throw ConnectionFailed("MySQL++ connection not established"); } else { return false; } } } string Connection::info() { const char* i = mysql_info(&mysql_); if (!i) { return string(); } else { return string(i); } } Query Connection::query() { return Query(this, throw_exceptions()); } bool Connection::set_option(Option option) { if (connected()) { // None of the argument-less options can be set once the // connection is up. return bad_option(option, opt_err_conn); } bool success = false; switch (option) { case opt_compress: success = set_option_impl(MYSQL_OPT_COMPRESS); break; case opt_named_pipe: success = set_option_impl(MYSQL_OPT_NAMED_PIPE); break; #if MYSQL_VERSION_ID >= 40101 case opt_use_result: success = set_option_impl(MYSQL_OPT_USE_RESULT); break; case opt_use_remote_connection: success = set_option_impl(MYSQL_OPT_USE_REMOTE_CONNECTION); break; case opt_use_embedded_connection: success = set_option_impl(MYSQL_OPT_USE_EMBEDDED_CONNECTION); break; case opt_guess_connection: success = set_option_impl(MYSQL_OPT_GUESS_CONNECTION); break; #endif default: return bad_option(option, opt_err_type); } if (success) { applied_options_.push_back(OptionInfo(option)); return true; } else { return bad_option(option, opt_err_value); } } bool Connection::set_option(Option option, const char* arg) { if (connected()) { // None of the options taking a char* argument can be set once // the connection is up. return bad_option(option, opt_err_conn); } bool success = false; switch (option) { case opt_init_command: success = set_option_impl(MYSQL_INIT_COMMAND, arg); break; case opt_read_default_file: success = set_option_impl(MYSQL_READ_DEFAULT_FILE, arg); break; case opt_read_default_group: success = set_option_impl(MYSQL_READ_DEFAULT_GROUP, arg); break; case opt_set_charset_dir: success = set_option_impl(MYSQL_SET_CHARSET_DIR, arg); break; case opt_set_charset_name: success = set_option_impl(MYSQL_SET_CHARSET_NAME, arg); break; #if MYSQL_VERSION_ID >= 40100 case opt_shared_memory_base_name: success = set_option_impl(MYSQL_SHARED_MEMORY_BASE_NAME, arg); break; #endif #if MYSQL_VERSION_ID >= 40101 case opt_set_client_ip: success = set_option_impl(MYSQL_SET_CLIENT_IP, arg); break; #endif default: return bad_option(option, opt_err_type); } if (success) { applied_options_.push_back(OptionInfo(option, arg)); return true; } else { return bad_option(option, opt_err_value); } } bool Connection::set_option(Option option, unsigned int arg) { if (connected()) { // None of the options taking an int argument can be set once // the connection is up. return bad_option(option, opt_err_conn); } bool success = false; switch (option) { case opt_connect_timeout: success = set_option_impl(MYSQL_OPT_CONNECT_TIMEOUT, &arg); break; case opt_local_infile: success = set_option_impl(MYSQL_OPT_LOCAL_INFILE, &arg); break; #if MYSQL_VERSION_ID >= 40100 case opt_protocol: success = set_option_impl(MYSQL_OPT_PROTOCOL, &arg); break; #endif #if MYSQL_VERSION_ID >= 40101 case opt_read_timeout: success = set_option_impl(MYSQL_OPT_READ_TIMEOUT, &arg); break; case opt_write_timeout: success = set_option_impl(MYSQL_OPT_WRITE_TIMEOUT, &arg); break; #endif default: return bad_option(option, opt_err_type); } if (success) { applied_options_.push_back(OptionInfo(option, arg)); return true; } else { return bad_option(option, opt_err_value); } } bool Connection::set_option(Option option, bool arg) { if (connected() && (option != opt_multi_statements)) { // We're connected and it isn't an option that can be set // after connection is up, so complain to user. return bad_option(option, opt_err_conn); } bool success = false; switch (option) { #if MYSQL_VERSION_ID >= 40101 case opt_secure_auth: success = set_option_impl(MYSQL_SECURE_AUTH, &arg); break; case opt_multi_statements: // If connection is up, set the flag immediately. If not, // and caller wants this turned on, pretend success so that // we store the info we need to turn this flag on when // bringing the connection up. (If the caller is turning it // off before conn comes up, we effectively ignore this, // because that's the default.) if (connected()) { success = set_option_impl(arg ? MYSQL_OPTION_MULTI_STATEMENTS_ON : MYSQL_OPTION_MULTI_STATEMENTS_OFF); } else { success = arg; } break; #endif #if MYSQL_VERSION_ID >= 50003 case opt_report_data_truncation: success = set_option_impl(MYSQL_REPORT_DATA_TRUNCATION, &arg); break; #endif #if MYSQL_VERSION_ID >= 50013 case opt_reconnect: success = set_option_impl(MYSQL_OPT_RECONNECT, &arg); break; #endif default: return bad_option(option, opt_err_type); } if (success) { applied_options_.push_back(OptionInfo(option, arg)); return true; } else { return bad_option(option, opt_err_value); } } bool Connection::set_option_default(Option option) { if (option_set(option)) { return true; } else { return set_option(option); } } template bool Connection::set_option_default(Option option, T arg) { if (option_set(option)) { return true; } else { return set_option(option, arg); } } bool Connection::set_option_impl(mysql_option moption, const void* arg) { return !mysql_options(&mysql_, moption, static_cast(arg)); } #if MYSQL_VERSION_ID >= 40101 bool Connection::set_option_impl(enum_mysql_set_option msoption) { return !mysql_set_server_option(&mysql_, msoption); } #endif bool Connection::bad_option(Option option, OptionError error) { if (throw_exceptions()) { ostringstream os; switch (error) { case opt_err_type: { // Option was set using wrong argument type OptionArgType type = option_arg_type(option); os << "option " << option; if (type == opt_type_none) { os << " does not take an argument"; } else { os << " requires an argument of type " << type; } break; } case opt_err_value: // C API rejected option, which probably indicates that // you passed a option that it doesn't understand. os << "option " << option << " not supported in MySQL " "C API v"; api_version(os); break; case opt_err_conn: os << "option " << option << " can only be set " "before connection is established"; break; } throw BadOption(os.str(), option); } return false; } Connection::OptionArgType Connection::option_arg_type(Option option) { if ((option > opt_FIRST) && (option < opt_COUNT)) { return legal_opt_arg_types_[option]; } else { // Non-optional exception. Something is wrong with the library // internals if this one is thrown. throw BadOption("bad value given to option_arg_type()", option); } } bool Connection::option_set(Option option) { for (OptionListIt it = applied_options_.begin(); it != applied_options_.end(); ++it) { if (it->option == option) { return true; } } return false; } void Connection::enable_ssl(const char* key, const char* cert, const char* ca, const char* capath, const char* cipher) { #if defined(HAVE_MYSQL_SSL_SET) mysql_ssl_set(&mysql_, key, cert, ca, capath, cipher); #endif } ostream& Connection::api_version(ostream& os) { const int major = MYSQL_VERSION_ID / 10000; const int minor = (MYSQL_VERSION_ID - (major * 10000)) / 100; const int bug = MYSQL_VERSION_ID - (major * 10000) - (minor * 100); os << major << '.' << minor << '.' << bug; return os; } int Connection::ping() { if (connected()) { return mysql_ping(&mysql_); } else { // Not connected, and we've forgotten everything we need in // order to re-connect, if we once were connected. return 1; } } } // end namespace mysqlpp