Connecting to MySQL in a ++ way

As we have seen in the previous post, connecting to a MySQL server through the connector/C is easy - but just a bit verbose.

Wouldn't be nice if we could use an object-oriented structure to delegate someone else all the trivial tasks and just care about the core activity?

Well, instead of using connector/C we could use connector/C++ (natively provided by Oracle-MySQL), or the third-party wrapper to connector/C named MySQL++.

For a few good reasons, I currently cannot do this smart move now, but what I can do is providing a little object oriented wrapper of mine to the native C API. The main advantage in this approach being that I'll something more to write in this blog.

So, what I'm going to do here is writing a class, named Connection, that would make possible reduce the effort for the client programmer who just wants to connect to a MySQL server on localhost to this sleek piece of code:

Connection connection("root", "password");

First thing: instead of relying on C-style error codes and messages, we are about to use exceptions. So, we create a specific exception, derived from std::exception, that would identify errors coming from our wrapper:

#pragma once // 1.

#include <exception>
#include <string>

class MySQLException : public std::exception
{
public:
MySQLException(const char* what) : exception(what) {}
MySQLException(const std::string& what) : exception(what.c_str()) {}
};

1. I'm developing for VC++, so as inclusion guard I use this pragma - not a good idea, if the code should be used on different architectures. Just assume this is not the case.

That was easy.

Second step is not that simple: we are about to design a first version of our Connection class.

Currently the requirements are not very demanding: we just want to be able to specify in the constructor user name and password, and leave all the other parameters as default. But it comes naturally to provide a way to set these "other" parameters before calling the constructor. I decided to make them static member variable of the class, and provide static setters to change them according to the requirements.

Then we'll have a constructor, accepting user and password as char*, that will call the mysql_init() and mysql_real_connect(); and a destructor, for the mysql_close() call. The beauty of having the MYSQL cleanup function in the Connection destructor is that we could stop worrying about forgetting to call it. It is done automatically when our Connection object goes out of scope.

Make sense also thinking about a way of converting the errors as generated by connector/C in a way that is easier manageable by the exception constructor. As saying: starting from the current error number and error description, we want to get a string containing both. We'll do this in a short private class function.

Obviously, in the private section of the class we'll put also the MYSQL object that is going to be used by all the related functions.

By the way, we don't want to have copies of a connection object, so we disallow copy constructor and assignment operator, declaring them private (with no definition). As stated by MySQL Reference Manual: "You should not try to make a copy of a MYSQL structure. There is no guarantee that such a copy will be usable".

Third step, let's write the code. Here is the complete class declaration:

#pragma once

#include <string>
#include "my_global.h"
#include "mysql.h"

class Connection
{
private:
// default parameters
static std::string host_;
static std::string db_;
static unsigned int port_;
static std::string socket_;
static unsigned long flags_;

MYSQL mysql_;

std::string error();

Connection& operator=(const Connection&); // no assignment
Connection(const Connection&); // no copy ctor
public:
static void setHost(const std::string& host) { host_ = host; }
static void setDatabase(const std::string& db) { db_ = db; }
static void setSocket(const std::string& socket) { socket_ = socket; }
static void setPort(const unsigned int port) { port_ = port; }
static void setFlags(const unsigned long flags) { flags_ = flags; }

Connection(const char* user, const char* password);
~Connection();
};

And here is the definition of the class members:

#include <sstream>
#include "Connection.h"
#include "MySQLException.h"

// default connection values
std::string Connection::host_ = "localhost";
std::string Connection::db_;
unsigned int Connection::port_ = 3306;
std::string Connection::socket_;
unsigned long Connection::flags_;

std::string Connection::error()
{
std::ostringstream oss;
oss << mysql_errno(&mysql_) << " " << mysql_error(&mysql_);
return oss.str();
}

Connection::Connection(const char* user, const char* password)
{
if(mysql_init(&mysql_) == 0)
throw MySQLException(error());

if(mysql_real_connect(&mysql_, host_.c_str(), user, password,
db_.c_str(), port_, socket_.c_str(), flags_) == 0)
{
throw MySQLException(error());
}
}

Connection::~Connection()
{
mysql_close(&mysql_);
}

Given that, we could actually test our connection with the single-liner we have written many line above, but it is more sensible giving some feedback to the output console and catching the possible exception. So we have this code:

#include <iostream>
#include "Connection.h"
#include "MySQLException.h"

using std::cout;
using std::endl;

void ms02()
{
try
{
cout << "Opening a connection to mysql" << endl;
Connection c("root", "password");
}
catch(const MySQLException& mse)
{
cout << "Error: " << mse.what() << endl;
return;
}

cout << "Job done." << endl;
}

No comments:

Post a Comment