changeset 177:f0cca031bcce

Improve Drivers
author David Demelier <markand@malikania.fr>
date Sat, 21 Sep 2013 09:25:28 +0200
parents ba62ab219fc4
children 2bfe43b85d7f
files C++/Driver.cpp C++/Driver.h C++/DriverPostgres.cpp C++/DriverPostgres.h
diffstat 4 files changed, 333 insertions(+), 311 deletions(-) [+]
line wrap: on
line diff
--- a/C++/Driver.cpp	Fri Sep 13 13:35:32 2013 +0200
+++ b/C++/Driver.cpp	Sat Sep 21 09:25:28 2013 +0200
@@ -23,104 +23,87 @@
 #include "Driver.h"
 
 /* ---------------------------------------------------------
+ * Query::Error class
+ * --------------------------------------------------------- */
+
+Query::Error::Error(const std::string &error)
+	: m_error(error)
+{
+}
+
+const char *Query::Error::Error::what() const throw()
+{
+	return m_error.c_str();
+}
+
+/* ---------------------------------------------------------
  * Query class
  * ---------------------------------------------------------*/
 
-Query::Query()
+void Query::assertRequest(int row, const std::string &column, ColumnType wanted)
 {
+	std::ostringstream oss;
+
+	// Out of bounds?
+	if (row < 0 || row >= countRows())
+	{
+		oss << "Invalid row index " << row;
+		oss << ", expected [0.." << countRows() << "]";
+
+		throw Error(oss.str());
+	}
+
+	// Not found or bad column?
+	if (type(column) != wanted)
+	{
+		oss << "Invalid or not found column `" << column << "'";
+
+		throw Error(oss.str());
+	}
 }
 
 Query::~Query()
 {
 }
 
-void Query::checkValidRequest(int row, const std::string &column, QueryCheck check)
+template <>
+bool Query::get(int row, const std::string &column)
 {
-	std::ostringstream oss;
+	assertRequest(row, column, ColumnType::Boolean);
 
-	switch (check)
-	{
-	case QueryCheck::InvalidColumn:
-		oss << "Invalid column '" << column << "'";
-		throw QueryError(oss.str());
-	case QueryCheck::InvalidRow:
-		oss << "Invalid row number '" << row << "'";
-		throw QueryError(oss.str());
-	case QueryCheck::InvalidType:
-		oss << "Column " << column;
-		oss << " type does not match requested type";
-
-		throw QueryError(oss.str());
-	default:
-		break;
-	}
+	return getBoolean(row, column);
 }
 
-bool Query::getBool(int row, const std::string &column)
+template <>
+Date Query::get(int row, const std::string &column)
 {
-	// Throw an exception on bad arguments
-	checkValidRequest(row, column,
-	    checkRequest(row, column, ColumnType::Boolean));
+	assertRequest(row, column, ColumnType::Date);
 
-	return checkBool(row, column);
-}
-
-Date Query::getDate(int row, const std::string &column)
-{
-	// Throw an exception on bad arguments
-	checkValidRequest(row, column,
-	    checkRequest(row, column, ColumnType::Date));
-
-	return checkDate(row, column);
+	return getDate(row, column);
 }
 
-double Query::getDouble(int row, const std::string &column)
+template <>
+double Query::get(int row, const std::string &column)
 {
-	// Throw an exception on bad arguments
-	checkValidRequest(row, column,
-	    checkRequest(row, column, ColumnType::Double));
+	assertRequest(row, column, ColumnType::Double);
 
-	return checkDouble(row, column);
-}
-
-int Query::getInt(int row, const std::string &column)
-{
-	// Throw an exception on bad arguments
-	checkValidRequest(row, column,
-	    checkRequest(row, column, ColumnType::Integer));
-
-	return checkInt(row, column);
+	return getDouble(row, column);
 }
 
-std::string Query::getString(int row, const std::string &column)
+template <>
+int Query::get(int row, const std::string &column)
 {
-	// Throw an exception on bad arguments
-	checkValidRequest(row, column,
-	    checkRequest(row, column, ColumnType::String));
+	assertRequest(row, column, ColumnType::Integer);
 
-	return checkString(row, column);
+	return getInt(row, column);
 }
 
-/* ---------------------------------------------------------
- * QueryError class
- * --------------------------------------------------------- */
-
-QueryError::QueryError()
+template <>
+std::string Query::get(int row, const std::string &column)
 {
-}
+	assertRequest(row, column, ColumnType::String);
 
-QueryError::QueryError(const std::string &error)
-	: m_error(error)
-{
-}
-
-QueryError::~QueryError()
-{
-}
-
-const char *QueryError::what() const throw()
-{
-	return m_error.c_str();
+	return getString(row, column);
 }
 
 /* --------------------------------------------------------
--- a/C++/Driver.h	Fri Sep 13 13:35:32 2013 +0200
+++ b/C++/Driver.h	Sat Sep 21 09:25:28 2013 +0200
@@ -24,26 +24,7 @@
 #include <memory>
 #include <string>
 
-class Query;
-
-class Date
-{
-private:
-	time_t m_timestamp;
-
-public:
-	Date()
-	{
-	}
-
-	Date(time_t)
-	{
-	}
-
-	~Date()
-	{
-	}
-};
+#include <common/Date.h>
 
 /**
  * @enum ColumnType
@@ -52,6 +33,7 @@
  * Used for the drivers.
  */
 enum class ColumnType {
+	Invalid,			//! not found
 	Boolean,			//! bool or 0 / 1
 	Date,				//! date see Common/Date.h
 	Double,				//! double
@@ -60,76 +42,56 @@
 };
 
 /**
- * @enum QueryCheck
- * @brief Result enumeration for check* functions
- *
- * This is used for the Query drivers check functions so we can now which
- * kind of error happened.
- */
-enum class QueryCheck {
-	NoError = 0,			//! success
-	InvalidColumn,			//! non existent column
-	InvalidRow,			//! out of range row
-	InvalidType,			//! bad column type requested
-};
-
-/**
- * @class QueryError
- * @brief Query exception on driver error
- *
- * Exception thrown usually when an error appeared in driver.
- */
-class QueryError : public std::exception
-{
-private:
-	std::string m_error;
-public:
-	QueryError();
-
-	QueryError(const std::string &error);
-
-	~QueryError();
-
-	virtual const char *what() const throw();
-};
-
-/**
  * @class Query
  * @brief Class for querying the database
+ *
+ * That class is returned when a SQL query succeed. It can retrieve the
+ * number of rows, columns and retrieve the results independantly from the
+ * driver.
+ *
+ * @see Driver::query
  */
 class Query {
+public:
+	typedef std::shared_ptr<Query> Ptr;
+
+	/**
+	 * @class Error
+	 * @brief Query exception on query error
+	 */
+	class Error : public std::exception
+	{
+	private:
+		std::string m_error;
+
+	public:
+		Error(const std::string &error);
+
+		virtual const char *what() const throw();
+	};
+
 private:
+
 	/**
 	 * Check if the request is valid and throws an exception
 	 * on error.
 	 *
 	 * @param row the row number
 	 * @param column the column name
-	 * @param check the result
-	 * @throw QueryError on erro
+	 * @param type
+	 * @throw Error on error
 	 */
-	void checkValidRequest(int row, const std::string &column, QueryCheck check);
+	void assertRequest(int row, const std::string &column, ColumnType type);
 
 protected:
 	/**
-	 * Check if the request is correct, if the row is not out of range
-	 * if the column exist and if the type is correct.
-	 *
-	 * @param row the row number
-	 * @param column the column name
-	 * @param type the type requested
-	 * @return true if request is correct
-	 */
-	virtual QueryCheck checkRequest(int row, const std::string &column, ColumnType type) = 0;
-
-	/**
 	 * Get a bool.
 	 *
 	 * @param row the row number
 	 * @param column the column
 	 * @return the value
 	 */
-	virtual bool checkBool(int row, const std::string &column) = 0;
+	virtual bool getBoolean(int row, const std::string &column) = 0;
 
 	/**
 	 * Get a Date.
@@ -138,7 +100,7 @@
 	 * @param column the column
 	 * @return the value
 	 */
-	virtual Date checkDate(int row, const std::string &column) = 0;
+	virtual Date getDate(int row, const std::string &column) = 0;
 
 	/**
 	 * Get a double.
@@ -147,7 +109,7 @@
 	 * @param column the column
 	 * @return the value
 	 */
-	virtual double checkDouble(int row, const std::string &column) = 0;
+	virtual double getDouble(int row, const std::string &column) = 0;
 
 	/**
 	 * Get a integer.
@@ -156,7 +118,7 @@
 	 * @param column the column
 	 * @return the value
 	 */
-	virtual int checkInt(int row, const std::string &column) = 0;
+	virtual int getInt(int row, const std::string &column) = 0;
 
 	/**
 	 * Get a string.
@@ -165,68 +127,45 @@
 	 * @param column the column
 	 * @return the value
 	 */
-	virtual std::string checkString(int row, const std::string &column) = 0;
+	virtual std::string getString(int row, const std::string &column) = 0;
 
 public:
-	Query(void);
-	virtual ~Query(void);
-
 	/**
-	 * Get bool at a specified row / column.
-	 *
-	 * @param row the row number
-	 * @param column the column
-	 * @return a boolean
-	 * @throw QueryError on error
+	 * Default destructor.
 	 */
-	bool getBool(int row, const std::string &column);
-
-	/**
-	 * Get a Date at a specified row / column.
-	 *
-	 * @param row the row number
-	 * @param column the column
-	 * @return a Date
-	 * @throw QueryError on error
-	 */
-	Date getDate(int row, const std::string &column);
+	virtual ~Query();
 
 	/**
-	 * Get a double at a specified row / column.
+	 * Get a variable from a row and column.
 	 *
-	 * @param row the row number
-	 * @param column the column
-	 * @return a double
-	 * @throw QueryError on error
+	 * Specialization available:
+	 *	- bool
+	 *	- Date
+	 *	- double
+	 *	- int
+	 *	- std::string
+	 *
+	 * @param row the row number (starts from 0)
+	 * @param column the the column name
+	 * @return the value
 	 */
-	double getDouble(int row, const std::string &column);
+	template <class T>
+	T get(int row, const std::string &column);
 
 	/**
-	 * Get an integer at a specified row / column.
+	 * Returns the type of a named column.
 	 *
-	 * @param row the row number
-	 * @param column the column
-	 * @return an integer
-	 * @throw QueryError on error
+	 * @param column the column name
+	 * @return the type
 	 */
-	int getInt(int row, const std::string &column);
-
-	/**
-	 * Get a string at a specified row / column.
-	 *
-	 * @param row the row number
-	 * @param column the column
-	 * @return a string
-	 * @throw QueryError on error
-	 */
-	std::string getString(int row, const std::string &column);
+	virtual ColumnType type(const std::string &column) const = 0;
 
 	/**
 	 * Tells how many rows has been fetched.
 	 *
 	 * @return the number of rows
 	 */
-	virtual int countRows(void) = 0;
+	virtual int countRows() = 0;
 
 	/**
 	 * Tells how many number of columns are present for each
@@ -234,7 +173,7 @@
 	 *
 	 * @return the number of columns
 	 */
-	virtual int countColumns(void) = 0;
+	virtual int countColumns() = 0;
 
 	/**
 	 * Tells if the column is null or not.
@@ -248,20 +187,45 @@
 	/**
 	 * Dump all rows and columns.
 	 */
-	virtual void dump(void) = 0;
+	virtual void dump() = 0;
 };
 
-class Driver {
+/**
+ * @class Driver
+ * @brief A generic SQL driver
+ *
+ * This class is used to connect to a database and execute SQL queries. It
+ * does not include any DBMS code and just call virtual functions for
+ * a simpler integration of new DBMS drivers.
+ */
+class Driver : public std::enable_shared_from_this<Driver> {
 protected:
 	std::string m_error;
 
+	/**
+	 * Default constructor. Should not be used.
+	 *
+	 * @see Driver::create
+	 */
+	Driver();
+
 public:
 	typedef std::map<std::string, std::string> Params;
+	typedef std::shared_ptr<Driver> Ptr;
 
 	/**
-	 * Default constructor.
+	 * Create a new Driver object, some queries need to do additional
+	 * internal requests so it's easier to use shared_ptr.
+	 *
+	 * @return a shared_ptr
 	 */
-	Driver();
+	template <class T>
+	static Ptr create()
+	{
+		T *t = new T();
+
+		return std::shared_ptr<T>(t);
+	}
 
 	/**
 	 * Virtual destructor.
@@ -273,7 +237,7 @@
 	 *
 	 * @return the error
 	 */
-	const std::string & getError() const;
+	const std::string &getError() const;
 
 	/**
 	 * Create a synchronous connection, it waits and block until
@@ -290,7 +254,7 @@
 	 * @return a result
 	 * @throw Query::Exception on failure
 	 */
-	virtual std::unique_ptr<Query> query(const std::string &command) = 0;
+	virtual Query::Ptr query(const std::string &command) = 0;
 
 	/**
 	 * Get the driver connection description.
@@ -298,6 +262,13 @@
 	 * @return the description
 	 */
 	virtual std::string description() const = 0;
+
+	/**
+	 * Get the driver version as a string.
+	 *
+	 * @return the version
+	 */
+	virtual std::string version() const = 0;
 };
 
 #endif // !_DRIVER_H_
--- a/C++/DriverPostgres.cpp	Fri Sep 13 13:35:32 2013 +0200
+++ b/C++/DriverPostgres.cpp	Sat Sep 21 09:25:28 2013 +0200
@@ -24,54 +24,72 @@
  * Query PostgreSQL (protected methods)
  * -------------------------------------------------------- */
 
-QueryCheck QueryPostgres::checkRequest(int row, const std::string &column, ColumnType type)
+bool QueryPostgres::getBoolean(int row, const std::string &column)
 {
-	QueryCheck ret = QueryCheck::InvalidType;
-	Oid pqType;
-	int colIndex;
-	bool success = false;
+	int idx = PQfnumber(m_result.get(), column.c_str());
+	std::string code = PQgetvalue(m_result.get(), row, idx);
 
-	// Invalid row
-	if (row >= PQntuples(m_result.get()))
-		return QueryCheck::InvalidRow;
-	if ((colIndex = PQfnumber(m_result.get(), column.c_str())) == -1)
-		return QueryCheck::InvalidColumn;
+	return code[0] == 't';
+}
 
-	pqType = PQftype(m_result.get(), colIndex);
-	switch (type)
+Date QueryPostgres::getDate(int row, const std::string &column)
+{
+	int idx = PQfnumber(m_result.get(), column.c_str());
+	long timestamp = static_cast<long>(time(0));
+
+	try
 	{
-	case ColumnType::Boolean:
-		success = (pqType == 16);
-		break;
-	case ColumnType::Date:
-		success = (pqType == 1082) || (pqType == 1083) || (pqType == 1114);
-		break;
-	case ColumnType::Double:
-		success = (pqType = 1700) || (pqType == 700) || (pqType == 701);
-		break;
-	case ColumnType::Integer:
-		success = (pqType == 20) || (pqType == 21) || (pqType == 23) || (pqType == 1700);
-		break;
-	case ColumnType::String:
-		success = (pqType == 25) || (pqType == 1042) || (pqType == 1043);
-		break;
-	default:
-		ret = QueryCheck::InvalidType;
+		std::ostringstream oss;
+		std::string value, req;
+
+		value = PQgetvalue(m_result.get(), row, idx);
+
+		/*
+		 * Convert the date using the SQL function so that user does
+		 * not require any explicit conversion itself.
+		 */
+		oss << "Select EXTRACT(EPOCH FROM TIMESTAMP '";
+		oss << value;
+		oss << "') AS RESULT";
+		req = oss.str();
+
+		Query::Ptr result = m_driver->query(req);
+		timestamp = result->get<double>(0, "RESULT");
 	}
+	catch (...) { }
 
-	// valid type requested?
-	if (success)
-		ret = QueryCheck::NoError;
+	return Date(static_cast<time_t>(timestamp));
+}
+
+double QueryPostgres::getDouble(int row, const std::string &column)
+{
+	int idx = PQfnumber(m_result.get(), column.c_str());
+
+	return std::stod(PQgetvalue(m_result.get(), row, idx));
+}
 
-	return ret;
+int QueryPostgres::getInt(int row, const std::string &column)
+{
+	int idx = PQfnumber(m_result.get(), column.c_str());
+
+	return std::stoi(PQgetvalue(m_result.get(), row, idx));
+
+}
+
+std::string QueryPostgres::getString(int row, const std::string &column)
+{
+	int idx = PQfnumber(m_result.get(), column.c_str());
+
+	return std::string(PQgetvalue(m_result.get(), row, idx));
 }
 
 /* --------------------------------------------------------
  * Query PostgreSQL (public methods)
  * -------------------------------------------------------- */
 
-QueryPostgres::QueryPostgres(PostgresResult result)
+QueryPostgres::QueryPostgres(Driver::Ptr driver, PostgresResult result)
 {
+	m_driver = driver;
 	m_result = std::move(result);
 }
 
@@ -79,6 +97,45 @@
 {
 }
 
+ColumnType QueryPostgres::type(const std::string &column) const
+{
+	ColumnType type;
+	int pqType, index;
+	
+	index = PQfnumber(m_result.get(), column.c_str());
+	pqType = PQftype(m_result.get(), index);
+	switch (pqType)
+	{
+	case 16:
+		type = ColumnType::Boolean;
+		break;
+	case 1082:
+	case 1083:
+	case 1114:
+		type = ColumnType::Date;
+		break;
+	case 1700:
+	case 700:
+	case 701:
+		type = ColumnType::Double;
+		break;
+	case 20:
+	case 21:
+	case 23:
+		type = ColumnType::Integer;
+		break;
+	case 25:
+	case 1042:
+	case 1043:
+		type = ColumnType::String;
+		break;
+	default:
+		type = ColumnType::Invalid;
+	}
+
+	return type;
+}
+
 int QueryPostgres::countRows()
 {
 	return PQntuples(m_result.get());
@@ -89,69 +146,6 @@
 	return PQnfields(m_result.get());
 }
 
-bool QueryPostgres::checkBool(int row, const std::string &column)
-{
-	int idx = PQfnumber(m_result.get(), column.c_str());
-	std::string code = PQgetvalue(m_result.get(), row, idx);
-
-	return code[0] == 't';
-}
-
-Date QueryPostgres::checkDate(int row, const std::string &column)
-{
-	int idx = PQfnumber(m_result.get(), column.c_str());
-	long timestamp = static_cast<long>(time(0));
-
-	try
-	{
-		timestamp = std::stol(PQgetvalue(m_result.get(), row, idx));
-	}
-	catch (std::invalid_argument)
-	{
-	}
-
-	return Date(static_cast<time_t>(timestamp));
-}
-
-double QueryPostgres::checkDouble(int row, const std::string &column)
-{
-	int idx = PQfnumber(m_result.get(), column.c_str());
-	double d = 0;
-
-	try
-	{
-		d = std::stod(PQgetvalue(m_result.get(), row, idx));
-	}
-	catch (std::invalid_argument)
-	{
-	}
-
-	return d;
-}
-
-int QueryPostgres::checkInt(int row, const std::string &column)
-{
-	int idx = PQfnumber(m_result.get(), column.c_str());
-	int i = 0;
-
-	try
-	{
-		i = std::stoi(PQgetvalue(m_result.get(), row, idx));
-	}
-	catch (std::invalid_argument)
-	{
-	}
-
-	return i;
-}
-
-std::string QueryPostgres::checkString(int row, const std::string &column)
-{
-	int idx = PQfnumber(m_result.get(), column.c_str());
-
-	return std::string(PQgetvalue(m_result.get(), row, idx));
-}
-
 bool QueryPostgres::isNull(int row, const std::string &column)
 {
 	int idx = PQfnumber(m_result.get(), column.c_str());
@@ -227,14 +221,14 @@
 	return true;
 }
 
-std::unique_ptr<Query> DriverPostgres::query(const std::string &cmd)
+Query::Ptr DriverPostgres::query(const std::string &cmd)
 {
 	PGresult *info;
 
 	// If NULL, the libpq said no memory
 	info = PQexec(m_connection.get(), cmd.c_str());
 	if (info == nullptr)
-		throw QueryError(strerror(ENOMEM));
+		throw Query::Error(strerror(ENOMEM));
 
 	// If an error occured
 	int errorCode = PQresultStatus(info);
@@ -242,10 +236,11 @@
 	{
 		std::string error = PQresultErrorMessage(info);
 		PQclear(info);
-		throw QueryError(error);
+		throw Query::Error(error);
 	}
 
-	return std::unique_ptr<Query>(new QueryPostgres(QueryPostgres::PostgresResult(info)));
+	return std::unique_ptr<Query>(new QueryPostgres(shared_from_this(),
+				      QueryPostgres::PostgresResult(info)));
 }
 
 std::string DriverPostgres::description() const
@@ -260,3 +255,14 @@
 
 	return oss.str();
 }
+
+std::string DriverPostgres::version() const
+{
+	std::ostringstream oss;
+
+	// TODO: ADD VERSION
+
+	oss << "PostgreSQL driver";
+
+	return oss.str();
+}
--- a/C++/DriverPostgres.h	Fri Sep 13 13:35:32 2013 +0200
+++ b/C++/DriverPostgres.h	Sat Sep 21 09:25:28 2013 +0200
@@ -23,6 +23,7 @@
 #ifndef _DRIVER_PG_H_
 #define _DRIVER_PG_H_
 
+#include <cerrno>
 #include <map>
 #include <memory>
 #include <string>
@@ -32,7 +33,8 @@
 #include "Driver.h"
 
 /**
- * Query implementation for PostgreSQL.
+ * @class QueryPostgres
+ * @brief Query implementation for PostgreSQL.
  */
 class QueryPostgres : public Query
 {
@@ -49,42 +51,80 @@
 
 private:
 	PostgresResult m_result;
+	Driver::Ptr m_driver;
 
 protected:
-	virtual QueryCheck checkRequest(int row, const std::string &column, ColumnType type);
-
-	virtual Date checkDate(int row, const std::string &column);
+	/**
+	 * @copydoc Query::getBoolean
+	 */
+	virtual bool getBoolean(int row, const std::string &column);
 
-	virtual bool checkBool(int row, const std::string &column);
-
-	virtual double checkDouble(int row, const std::string &column);
+	/**
+	 * @copydoc Query::getDate
+	 */
+	virtual Date getDate(int row, const std::string &column);
 
-	virtual int checkInt(int row, const std::string &column);
-
-	virtual std::string checkString(int row, const std::string &column);
+	/**
+	 * @copydoc Query::getDouble
+	 */
+	virtual double getDouble(int row, const std::string &column);
 
-	// Avoid copy
-	QueryPostgres(const QueryPostgres &src);
+	/**
+	 * @copydoc Query::getInt
+	 */
+	virtual int getInt(int row, const std::string &column);
 
-	// Avoid copy
-	QueryPostgres & operator=(const QueryPostgres &src);
+	/**
+	 * @copydoc Query::getString
+	 */
+	virtual std::string getString(int row, const std::string &column);
 
 public:
-	QueryPostgres(PostgresResult);
+	/**
+	 * Constructor used by DriverPostgres
+	 */
+	QueryPostgres(Driver::Ptr driver, PostgresResult result);
+
+	/**
+	 * Default destructor.
+	 */
 	~QueryPostgres();
 
+	/**
+	 * @copydoc Query::type
+	 */
+	virtual ColumnType type(const std::string &column) const;
+
+	/**
+	 * @copydoc Query::countRows
+	 */
 	virtual int countRows();
 
+	/**
+	 * @copydoc Query::countColumns
+	 */
 	virtual int countColumns();
 
+	/**
+	 * @copydoc Query::isNull
+	 */
 	virtual bool isNull(int row, const std::string &column);
 
+	/**
+	 * @copydoc Query::dump
+	 */
 	virtual void dump();
 };
 
+/**
+ * @class DriverPostgres
+ * @brief Driver implementation for PostgreSQL.
+ */
 class DriverPostgres : public Driver
 {
 public:
+	friend class Driver;
+
 	struct PGDeleter {
 		void operator()(PGconn *conn) {
 			PQfinish(conn);
@@ -105,15 +145,37 @@
 	 */
 	std::string convert(Params &settings);
 
+protected:
+	/**
+	 * @copytoc Driver::Driver
+	 */
+	DriverPostgres();
+
 public:
-	DriverPostgres();
+	/**
+	 * @copydoc Driver::~Driver
+	 */
 	~DriverPostgres();
 
+	/**
+	 * @copydoc Driver::connect
+	 */
 	virtual bool connect(const Params &params);
 
-	virtual std::unique_ptr<Query> query(const std::string &command);
+	/**
+	 * @copydoc Driver::query
+	 */
+	virtual Query::Ptr query(const std::string &command);
 
+	/**
+	 * @copydoc Driver::description
+	 */
 	virtual std::string description() const;
+
+	/**
+	 * @copydoc Driver::version
+	 */
+	virtual std::string version() const;
 };
 
 #endif // !_DRIVER_PG_H_