view C++/DriverPostgres.cpp @ 296:5806c767aec7

Zip: fix resize if read less than requested Task: #312
author David Demelier <markand@malikania.fr>
date Thu, 13 Nov 2014 21:10:13 +0100
parents 82510454e772
children
line wrap: on
line source

/*
 * DriverPostgres.cpp -- PostgreSQL driver
 *
 * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <cerrno>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <vector>

#include <libpq-fe.h>

#include "DriverPostgres.h"

namespace {

using PostgresResult	= std::shared_ptr<PGresult>;
using PostgresConn	= std::shared_ptr<PGconn>;

}

/**
 * @class QueryPostgres
 * @brief Query implementation for PostgreSQL.
 */
class QueryPostgresImpl : public DriverQuery::Impl {
private:
	PostgresConn	m_connection;
	PostgresResult	m_result;

public:
	/**
	 * Constructor used by DriverPostgres
	 *
	 * @param result the result
	 */
	QueryPostgresImpl(PostgresConn conn, PostgresResult result);

	/**
	 * @copydoc Query::getBoolean
	 */
	virtual bool getBoolean(int row, const std::string &column);

	/**
	 * @copydoc Query::getDate
	 */
	virtual time_t getDate(int row, const std::string &column);

	/**
	 * @copydoc Query::getDouble
	 */
	virtual double getDouble(int row, const std::string &column);

	/**
	 * @copydoc Query::getInt
	 */
	virtual int getInt(int row, const std::string &column);

	/**
	 * @copydoc Query::getString
	 */
	virtual std::string getString(int row, const std::string &column);

	/**
	 * @copydoc Query::type
	 */
	virtual DriverColumn 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();
};

QueryPostgresImpl::QueryPostgresImpl(PostgresConn conn, PostgresResult result)
	: m_connection(conn)
	, m_result(result)
{
}

DriverColumn QueryPostgresImpl::type(const std::string &column) const
{
	DriverColumn type;
	int pqType, index;
	
	index = PQfnumber(m_result.get(), column.c_str());
	pqType = PQftype(m_result.get(), index);
	switch (pqType) {
	case 16:
		type = DriverColumn::Boolean;
		break;
	case 1082:
	case 1083:
	case 1114:
	case 1184:
		type = DriverColumn::Date;
		break;
	case 1700:
	case 700:
	case 701:
		type = DriverColumn::Double;
		break;
	case 20:
	case 21:
	case 23:
		type = DriverColumn::Integer;
		break;
	case 25:
	case 1042:
	case 1043:
		type = DriverColumn::String;
		break;
	default:
		type = DriverColumn::Invalid;
	}

	return type;
}

int QueryPostgresImpl::countRows()
{
	return PQntuples(m_result.get());
}

int QueryPostgresImpl::countColumns()
{
	return PQnfields(m_result.get());
}

bool QueryPostgresImpl::isNull(int row, const std::string &column)
{
	int idx = PQfnumber(m_result.get(), column.c_str());

	return PQgetisnull(m_result.get(), row, idx) == 1;
}

void QueryPostgresImpl::dump(void)
{
	std::cout << "Dumping PostgreSQL result, ";
	std::cout << countRows() << " rows, ";
	std::cout << countColumns() << " columns" << std::endl;

	for (int r = 0; r < countRows(); ++r) {
		std::cout << "Dumping row " << r << std::endl;
		std::cout << "==============================" << std::endl;

		for (int c = 0; c < countColumns(); ++c) {
			std::cout << "\t" << PQfname(m_result.get(), c);
			std::cout << " = " << PQgetvalue(m_result.get(), r, c) << std::endl;
		}
	}
}

bool QueryPostgresImpl::getBoolean(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';
}

time_t QueryPostgresImpl::getDate(int row, const std::string &column)
{
	int idx = PQfnumber(m_result.get(), column.c_str());
	time_t timestamp = std::time(nullptr);

	try {
		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.
		 */
		printf("%s\n", value.c_str());
		oss << "Select EXTRACT(EPOCH FROM TIMESTAMP WITH TIME ZONE '";
		oss << value;
		oss << "') AS RESULT";
		req = oss.str();

		auto info = PQexec(m_connection.get(), oss.str().c_str());
		auto status = PQresultStatus(info);

		if (info != nullptr && (status == PGRES_COMMAND_OK ||
		    status == PGRES_TUPLES_OK))
		{
			timestamp = atoi(PQgetvalue(info, 0, 0));
			PQclear(info);
		}
	} catch (...) { }

	return timestamp;
}

double QueryPostgresImpl::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));
}

int QueryPostgresImpl::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 QueryPostgresImpl::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));
}

/* --------------------------------------------------------
 * Request PostgreSQL Impl
 * -------------------------------------------------------- */

/**
 * @class RequestPostgres
 * @brief Request implementation for PostgreSQL.
 */
class RequestPostgres : public DriverRequest::Impl {
private:
	std::shared_ptr<PGconn> m_connection;

protected:
	/**
	 * @copydoc Request::bindBoolean
	 */
	virtual std::string bindBoolean(bool value);

	/**
	 * @copydoc Request::bindDate
	 */
	virtual std::string bindDate(time_t value);

	/**
	 * @copydoc Request::bindDouble
	 */
	virtual std::string bindDouble(double value);

	/**
	 * @copydoc Request::bindInteger
	 */
	virtual std::string bindInteger(int value);

	/**
	 * @copydoc Request::bindString
	 */
	virtual std::string bindString(std::string value);

public:
	/**
	 * Construct a request for PostgreSQL.
	 *
	 * @param conn the connection
	 * @param command the command
	 */
	RequestPostgres(PostgresConn &conn);
};

RequestPostgres::RequestPostgres(PostgresConn &conn)
{
	m_connection = conn;
}

std::string RequestPostgres::bindBoolean(bool value)
{
	return (value) ? "'t'" : "'f'";
}

std::string RequestPostgres::bindDate(time_t value)
{
	std::ostringstream oss;

	oss << "to_timestamp(" << value << ")";

	return oss.str();
}

std::string RequestPostgres::bindDouble(double value)
{
	return std::to_string(value);
}

std::string RequestPostgres::bindInteger(int value)
{
	return std::to_string(value);
}

std::string RequestPostgres::bindString(std::string value)
{
	std::string result;
	char *tmp;

	tmp = PQescapeLiteral(m_connection.get(), value.c_str(), value.length());
	if (tmp == nullptr)
		return "";

	result = std::string(tmp);
	PQfreemem(tmp);

	return result;
}

/* --------------------------------------------------------
 * Driver PostgreSQL impl
 * -------------------------------------------------------- */

/**
 * @class DriverPostgres
 * @brief Driver implementation for PostgreSQL.
 */
class DriverPostgresImpl : public Driver::Impl {
private:
	PostgresConn m_connection;

	/**
	 * Convert the Params from the config
	 * to the PostgreSQL string.
	 *
	 * @param settings the table
	 * @return a string to be used
	 */
	std::string convert(Driver::Params &settings);

public:
	/**
	 * @copydoc Driver::connect
	 */
	virtual void connect(const Driver::Params &params);

	/**
	 * @copydoc Driver::prepare
	 */
	virtual DriverRequest prepare(const std::string &command);

	/**
	 * @copydoc Driver::query
	 */
	virtual DriverQuery query(const std::string &command);

	/**
	 * @copydoc Driver::description
	 */
	virtual std::string description() const;

	/**
	 * @copydoc Driver::version
	 */
	virtual std::string version() const;
};

std::string DriverPostgresImpl::convert(Driver::Params &params)
{
	std::ostringstream oss;
	std::vector<std::string> required { "host", "port", "user", "database", "password" };

	for (auto s : required)
		if (params.count(s) <= 0)
			throw std::runtime_error("missing parameter " + s);

	oss << "host = " << params["host"] << " ";
	oss << "port = " << params["port"] << " ";
	oss << "user = " << params["user"] << " ";
	oss << "dbname = " << params["database"] << " ";
	oss << "password = " << params["password"];

	return oss.str();
}

void DriverPostgresImpl::connect(const Driver::Params &params)
{
	auto copy = params;
	auto conn = PQconnectdb(convert(copy).c_str());

	if (conn == nullptr)
		throw std::runtime_error(std::strerror(ENOMEM));

	if (PQstatus(conn) == CONNECTION_BAD) {
		auto error = PQerrorMessage(conn);
		PQfinish(conn);

		throw std::runtime_error(error);
	}

	m_connection = std::shared_ptr<PGconn>(conn, PQfinish);
}

DriverRequest DriverPostgresImpl::prepare(const std::string &command)
{
	return DriverRequest(std::make_shared<RequestPostgres>(m_connection), command);
}

DriverQuery DriverPostgresImpl::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 std::runtime_error(strerror(ENOMEM));

	// If an error occured
	int errorCode = PQresultStatus(info);
	if (errorCode != PGRES_COMMAND_OK && errorCode != PGRES_TUPLES_OK) {
		auto error = PQresultErrorMessage(info);
		PQclear(info);

		throw std::runtime_error(error);
	}

	auto result = std::shared_ptr<PGresult>(info, PQclear);
	auto impl = std::make_shared<QueryPostgresImpl>(m_connection, result);

	return DriverQuery(impl);
}

std::string DriverPostgresImpl::description() const
{
	std::ostringstream oss;

	oss << "Connected on PostgreSQL database: " << std::endl;
	oss << "  host: " << PQhost(m_connection.get()) << std::endl;
	oss << "  port: " << PQport(m_connection.get()) << std::endl;
	oss << "  user: " << PQuser(m_connection.get()) << std::endl;
	oss << "  database: " << PQdb(m_connection.get()) << std::endl;

	return oss.str();
}

std::string DriverPostgresImpl::version() const
{
	std::ostringstream oss;

	oss << "PostgreSQL driver (version " << PQlibVersion() << ")";

	return oss.str();
}

/* --------------------------------------------------------
 * Driver PostgreSQL
 * -------------------------------------------------------- */

DriverPostgres::DriverPostgres()
{
	m_impl = std::make_shared<DriverPostgresImpl>();
}