Pages

Wrapping MYSQL_RES in a class

A bad thing about the Connection::query() we developed in the previous post, is that it returns a pointer to MYSQL_RES. This is bad because we are exposing the internal details of MySQL Connector/C to the client programmer; because we are relying on him to check if the pointer is not a NULL before using it; and also because, if the MYSQL_RES we are passing him is not NULL, we should rely on him for its destruction, via a call to mysql_free_result().

If he forgets to do the check against NULL, we risk our application to crash.
If he forgets to call mysql_free_result(), we'll have a memory leak.

To solve this issues we wrap the MYSQL_RES in a class, ResultSet, and we use a variation of the Null Object pattern, to let a dummy ResultSet object to be generated when actually no ResultSet is available.

A decision we have to take designing the ResultSet class is if we want to allow that an object of this class could be copied or not. A conservative suggestion would be not to allow that, the reason being that this would save us to mess around with the internal details of the MYSQL_RES object. On the other side, it could be useful to give the chance to the user to move around ResultSet object.

I have decided for an intermediate solution: a ResultSet object could be moved using the same strategy used, for instance, by std::auto_ptr. That means, after copied, the source object would be nullified.

Let's see a first implementation for the class:

class ResultSet
{
private:
MYSQL_RES* rs_;

void moveToThis(ResultSet& rhs); // 1.
public:
ResultSet() : rs_(0) {} // 2.
ResultSet(MYSQL_RES* rs) : rs_(rs) {} // 3.
ResultSet(ResultSet& rhs);
ResultSet& operator=(ResultSet& rhs);
~ResultSet();

_OPERATOR_BOOL() const { return (rs_ != 0 ? _CONVERTIBLE_TO_TRUE : 0); } // 4.
};

1. Common functionality used by both copy ctor and assignment operator.
2. Null Object ctor.
3. "Normal" ctor.
4. operator to test if actually we have a "real" object or a null one. It is based on defines for the MSVC compiler. To do something portable I could have used the boost library (please assume here that was not possible)

And here is the implementation for the declared methods:

void ResultSet::moveToThis(ResultSet& rhs)
{
rs_ = rhs.rs_;
rhs.rs_ = 0;
}

ResultSet::ResultSet(ResultSet& rhs)
{
moveToThis(rhs);
}

ResultSet& ResultSet::operator=(ResultSet& rhs)
{
moveToThis(rhs);
return *this;
}

ResultSet::~ResultSet()
{
mysql_free_result(rs_); // 1.
}

1. Calling mysql_free_result() with NULL is a no-op, so this piece of code works fine also for the Null Object.

So, now we can rewrite Connection::query() in this way:

ResultSet Connection::query(const char* command)
{
if(mysql_query(&mysql_, command))
throw MySQLException(error());

// if no resultset is expected, return a dummy
if(mysql_.field_count == 0)
return ResultSet();

MYSQL_RES* rs = mysql_store_result(&mysql_);
return ResultSet(rs);
}

No comments:

Post a Comment