/*
 * Copyright (C) 2008 Emweb bv, Herent, Belgium.
 *
 * See the LICENSE file for terms of use.
 *
 * Contributed by: Hilary Cheng
 */
#include "Wt/WConfig.h"

#ifdef WT_WIN32
#define NOMINMAX
// WinSock2.h warns that it should be included before windows.h
#include <WinSock2.h>
#endif // WT_WIN32

#include "Wt/Dbo/backend/Postgres.h"
#include "Wt/Dbo/Exception.h"
#include "Wt/Dbo/Logger.h"
#include "Wt/Dbo/StringStream.h"

#include <libpq-fe.h>
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstdio>
#include <iostream>
#include <iomanip>
#include <vector>
#include <sstream>
#include <cstring>
#include <ctime>

/* While <charconv> is part of C++17, it can be used by earlier versions as an extension.
 * This entails that it is possible some implementations lack features. Hence the
 * #ifdef __cpp_lib_to_chars additional check.
 */
#if defined __has_include
#  if __has_include (<charconv>)
#    include <charconv>
#    ifdef __cpp_lib_to_chars
#      define WT_CPP_LIB_TO_CHARS
#    endif
#  endif
#endif

#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/qi_parse.hpp>
#include <boost/spirit/include/qi_numeric.hpp>

#include "Wt/Date/date.h"

#ifdef WT_WIN32
#define snprintf _snprintf
#define strcasecmp _stricmp
#else // WT_WIN32
#include <sys/select.h>
#endif // WT_WIN32

#define BYTEAOID 17

namespace karma = boost::spirit::karma;

namespace {
  template<typename ResultType, typename SpiritType>
  ResultType convert(const char *fname, const SpiritType &t, const std::string& v)
  {
    auto is_space = [](char c) { return c == ' '; };
    auto it = std::find_if_not(v.cbegin(), v.cend(), is_space);
    ResultType result{0};
    bool success = it < v.cend() &&
                   boost::spirit::qi::parse(it, v.cend(), t, result) &&
                   std::all_of(it, v.cend(), is_space);
    if (!success)
      throw std::invalid_argument(std::string(fname) + "() of " + v + " failed");
    return result;
  }

  inline struct timeval toTimeval(std::chrono::microseconds ms)
  {
    std::chrono::seconds s = date::floor<std::chrono::seconds>(ms);
    struct timeval result;
    result.tv_sec = s.count();
    result.tv_usec = (ms - s).count();
    return result;
  }

  // adjust rendering for JS flaots
  template <typename T, int Precision>
  struct PostgresPolicy : karma::real_policies<T>
  {
    // not 'nan', but 'NaN'
    template <typename CharEncoding, typename Tag, typename OutputIterator>
    static bool nan (OutputIterator& sink, T, bool)
    {
      return karma::string_inserter<CharEncoding, Tag>::call(sink, "NaN");
    }

    // not 'inf', but 'Infinity'
    template <typename CharEncoding, typename Tag, typename OutputIterator>
    static bool inf (OutputIterator& sink, T n, bool force_sign)
    {
      return karma::sign_inserter::call(sink, false, (n<0), force_sign) &&
        karma::string_inserter<CharEncoding, Tag>::call(sink, "Infinity");
    }

    static int floatfield(T t) {
      return (t != 0.0) && ((t < 0.001) || (t > 1E8)) ?
        karma::real_policies<T>::fmtflags::scientific :
        karma::real_policies<T>::fmtflags::fixed;
    }

    // 7 significant numbers; about float precision
    static unsigned precision(T) { return Precision; }

  };

  using PostgresReal = karma::real_generator<float, PostgresPolicy<float, 7> >;
  using PostgresDouble = karma::real_generator<double, PostgresPolicy<double, 15> >;

  static inline std::string double_to_s(const double d)
  {
#ifdef WT_CPP_LIB_TO_CHARS
    std::array<char, 30> buf;
    auto returnValue = std::to_chars(buf.data(), buf.data() + buf.size(), d);

    if (returnValue.ec != std::errc()) {
      throw std::invalid_argument(
                "double_to_s: to_chars failed for double (as to_string() '" +
          std::to_string(d)  + "'), err: " + std::error_condition(returnValue.ec).message());
    }
    return std::string(buf.data(), returnValue.ptr - buf.data());
#else
    char buf[30];
    char *p = buf;
    if (d != 0) {
      karma::generate(p, PostgresDouble(), d);
    } else {
      *p++ = '0';
    }
    *p = '\0';
    return std::string(buf, p);
#endif
  }

  static inline std::string float_to_s(const float f)
  {
#ifdef WT_CPP_LIB_TO_CHARS
    std::array<char, 30> buf;
    auto returnValue = std::to_chars(buf.data(), buf.data() + buf.size(), f);

    if (returnValue.ec != std::errc()) {
      throw std::invalid_argument(
                "float_to_s: to_chars failed for float (as to_string() '" +
          std::to_string(f)  + "'), err: " + std::error_condition(returnValue.ec).message());
    }
    return std::string(buf.data(), returnValue.ptr - buf.data());
#else
    char buf[30];
    char *p = buf;
    if (f != 0) {
      karma::generate(p, PostgresReal(), f);
    } else {
      *p++ = '0';
    }
    *p = '\0';
    return std::string(buf, p);
#endif
  }
}

namespace Wt {
  namespace Dbo {

LOGGER("Dbo.backend.Postgres");

    namespace backend {

// do not reconnect in a transaction unless we exceed the lifetime by 120s.
const std::chrono::seconds TRANSACTION_LIFETIME_MARGIN = std::chrono::seconds(120);

class PostgresException : public Exception
{
public:
  PostgresException(const std::string& msg)
    : Exception(msg)
  { }

  PostgresException(const std::string& msg, const std::string& code)
    : Exception(msg, code)
  { }
};

class PostgresStatement final : public SqlStatement
{
public:
  PostgresStatement(Postgres& conn, const std::string& sql)
    : conn_(conn),
      sql_(sql)
  {
    convertToNumberedPlaceholders();

    lastId_ = -1;
    row_ = affectedRows_ = 0;
    result_ = nullptr;

    paramValues_ = nullptr;
    paramTypes_ = paramLengths_ = paramFormats_ = nullptr;
    columnCount_ = 0;

    snprintf(name_, 64, "SQL%p%08X", (void*)this, rand());

    LOG_DEBUG(this << " for: " << sql_);

    state_ = Done;
  }

  virtual ~PostgresStatement()
  {
    if (result_)
      PQclear(result_);
    delete[] paramValues_;
    delete[] paramTypes_;
  }

  virtual void reset() override
  {
    params_.clear();

    state_ = Done;
  }

  void rebuild()
  {
    if (result_) {
      PQclear(result_);
      result_ = 0;
      delete[] paramValues_;
      paramValues_ = 0;
      delete[] paramTypes_;
      paramTypes_ = paramLengths_ = paramFormats_ = 0;
    }
  }

  virtual void bind(int column, const std::string& value) override
  {
    LOG_DEBUG(this << " bind " << column << " " << value);

    setValue(column, value);
  }

  virtual void bind(int column, short value) override
  {
    bind(column, static_cast<int>(value));
  }

  virtual void bind(int column, int value) override
  {
    LOG_DEBUG(this << " bind " << column << " " << value);

    setValue(column, std::to_string(value));
  }

  virtual void bind(int column, long long value) override
  {
    LOG_DEBUG(this << " bind " << column << " " << value);

    setValue(column, std::to_string(value));
  }

  virtual void bind(int column, float value) override
  {
    LOG_DEBUG(this << " bind " << column << " " << value);

    setValue(column, float_to_s(value));
  }

  virtual void bind(int column, double value) override
  {
    LOG_DEBUG(this << " bind " << column << " " << value);

    setValue(column, double_to_s(value));
  }

  virtual void bind(int column, const std::chrono::duration<int, std::milli> & value) override
  {
    auto absValue = value < std::chrono::milliseconds::zero() ? -value : value;
    auto hours = date::floor<std::chrono::hours>(absValue);
    auto minutes = date::floor<std::chrono::minutes>(absValue) - hours;
    auto seconds = date::floor<std::chrono::seconds>(absValue) - hours - minutes;
    auto milliseconds = date::floor<std::chrono::milliseconds>(absValue) - hours - minutes - seconds;

    std::stringstream ss;
    ss.imbue(std::locale::classic());
    if (absValue != value)
      ss << '-';
    ss << std::setfill('0')
       << std::setw(2) << hours.count() << ':'
       << std::setw(2) << minutes.count() << ':'
       << std::setw(2) << seconds.count() << '.'
       << std::setw(3) << milliseconds.count();

    LOG_DEBUG(this << " bind " << column << " " << ss.str());

    setValue(column, ss.str());
  }

  virtual void bind(int column, const std::chrono::system_clock::time_point& value,
                    SqlDateTimeType type) override
  {
    std::stringstream ss;
    ss.imbue(std::locale::classic());
    if (type == SqlDateTimeType::Date) {
      auto daypoint = date::floor<date::days>(value);
      auto ymd = date::year_month_day(daypoint);
      ss << (int)ymd.year() << '-' << (unsigned)ymd.month() << '-' << (unsigned)ymd.day();
    } else {
      auto daypoint = date::floor<date::days>(value);
      auto ymd = date::year_month_day(daypoint);
      auto tod = date::make_time(value - daypoint);
      ss << (int)ymd.year() << '-' << (unsigned)ymd.month() << '-' << (unsigned)ymd.day() << ' ';
      ss << std::setfill('0')
         << std::setw(2) << tod.hours().count() << ':'
         << std::setw(2) << tod.minutes().count() << ':'
         << std::setw(2) << tod.seconds().count() << '.'
         << std::setw(3) << date::floor<std::chrono::milliseconds>(tod.subseconds()).count();
      /*
       * Add explicit timezone offset. Postgres will ignore this for a TIMESTAMP
       * column, but will treat the timestamp as UTC in a TIMESTAMP WITH TIME
       * ZONE column -- possibly in a legacy table.
       */
      ss << "+00";
    }
    LOG_DEBUG(this << " bind " << column << " " << ss.str());
    setValue(column, ss.str());
  }

  virtual void bind(int column, const std::vector<unsigned char>& value) override
  {
    LOG_DEBUG(this << " bind " << column << " (blob, size=" <<
              value.size() << ")");

    for (int i = (int)params_.size(); i <= column; ++i)
      params_.push_back(Param());

    Param& p = params_[column];
    p.value.resize(value.size());
    if (value.size() > 0)
      std::memcpy(const_cast<char *>(p.value.data()), &(*value.begin()),
             value.size());
    p.isbinary = true;
    p.isnull = false;

    // FIXME if first null was bound, check here and invalidate the prepared
    // statement if necessary because the type changes
  }

  virtual void bindNull(int column) override
  {
    LOG_DEBUG(this << " bind " << column << " null");

    for (int i = (int)params_.size(); i <= column; ++i)
      params_.push_back(Param());

    params_[column].isnull = true;
  }

  virtual void execute() override
  {
    conn_.checkConnection(TRANSACTION_LIFETIME_MARGIN);

    if (conn_.showQueries())
      LOG_INFO(sql_);

    if (!result_) {
      paramValues_ = new char *[params_.size()];

      for (unsigned i = 0; i < params_.size(); ++i) {
        if (params_[i].isbinary) {
          paramTypes_ = new int[params_.size() * 3];
          paramLengths_ = paramTypes_ + params_.size();
          paramFormats_ = paramLengths_ + params_.size();
          for (unsigned j = 0; j < params_.size(); ++j) {
            paramTypes_[j] = params_[j].isbinary ? BYTEAOID : 0;
            paramFormats_[j] = params_[j].isbinary ? 1 : 0;
            paramLengths_[j] = 0;
          }

          break;
        }
      }

      result_ = PQprepare(conn_.connection(), name_, sql_.c_str(),
                          paramTypes_ ? params_.size() : 0, (Oid *)paramTypes_);
      handleErr(PQresultStatus(result_), result_);
      columnCount_ = PQnfields(result_);
    }

    for (unsigned i = 0; i < params_.size(); ++i) {
      if (params_[i].isnull)
        paramValues_[i] = nullptr;
      else
        if (params_[i].isbinary) {
          paramValues_[i] = const_cast<char *>(params_[i].value.data());
          paramLengths_[i] = params_[i].value.length();
        } else
          paramValues_[i] = const_cast<char *>(params_[i].value.c_str());
    }

    int err = PQsendQueryPrepared(conn_.connection(), name_, params_.size(),
                                  paramValues_, paramLengths_, paramFormats_, 0);
    if (err != 1)
      throw PostgresException(PQerrorMessage(conn_.connection()));

    if (conn_.timeout() > std::chrono::microseconds{0}) {
      fd_set rfds;
      FD_ZERO(&rfds);
      FD_SET(PQsocket(conn_.connection()), &rfds);
      struct timeval timeout = toTimeval(conn_.timeout());

      for (;;) {
        int result = select(FD_SETSIZE, &rfds, 0, 0, &timeout);

        if (result == 0) {
          std::cerr << "Postgres: timeout while executing query" << std::endl;
          conn_.disconnect();
          throw PostgresException("Database timeout");
        } else if (result == -1) {
          if (errno != EINTR) {
            perror("select");
            throw PostgresException("Error waiting for result");
          } else {
            // EINTR, try again
          }
        } else {
          err = PQconsumeInput(conn_.connection());
          if (err != 1)
            throw PostgresException(PQerrorMessage(conn_.connection()));

          if (PQisBusy(conn_.connection()) != 1)
            break;
        }
      }
    }

    std::string error;

    PQclear(result_);
    result_ = PQgetResult(conn_.connection());

    row_ = 0;
    if (PQresultStatus(result_) == PGRES_COMMAND_OK) {
      std::string s = PQcmdTuples(result_);
      if (!s.empty())
        affectedRows_ = std::stoi(s);
      else
        affectedRows_ = 0;
    } else if (PQresultStatus(result_) == PGRES_TUPLES_OK)
      affectedRows_ = PQntuples(result_);

    columnCount_ = PQnfields(result_);

    bool isInsertReturningId = false;
    if (affectedRows_ == 1) {
      const std::string returning = " returning ";
      std::size_t j = sql_.rfind(returning);
      if (j != std::string::npos
          && sql_.find(' ', j + returning.length()) == std::string::npos)
        isInsertReturningId = true;
    }

    if (isInsertReturningId) {
      state_ = NoFirstRow;
      if (PQntuples(result_) == 1 && PQnfields(result_) == 1) {
        lastId_ = std::stoll(PQgetvalue(result_, 0, 0));
      }
    } else {
      if (PQntuples(result_) == 0) {
        state_ = NoFirstRow;
      } else {
        state_ = FirstRow;
      }
    }

    PGresult *nullResult = PQgetResult(conn_.connection());
    if (nullResult != 0) {
      throw PostgresException("PQgetResult() returned more results");
    }

    handleErr(PQresultStatus(result_), result_);
  }

  virtual long long insertedId() override
  {
    return lastId_;
  }

  virtual int affectedRowCount() override
  {
    return affectedRows_;
  }

  virtual bool nextRow() override
  {
    switch (state_) {
    case NoFirstRow:
      state_ = Done;
      return false;
    case FirstRow:
      state_ = NextRow;
      return true;
    case NextRow:
      if (row_ + 1 < PQntuples(result_)) {
        row_++;
        return true;
      } else {
        state_ = Done;
        return false;
      }
      break;
    case Done:
      throw PostgresException("Postgres: nextRow(): statement already "
                              "finished");
    }

    return false;
  }

  virtual int columnCount() const override {
    return columnCount_;
  }

  virtual bool getResult(int column, std::string *value, WT_MAYBE_UNUSED int size) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;

    *value = PQgetvalue(result_, row_, column);

    LOG_DEBUG(this << " result string " << column << " " << *value);

    return true;
  }

  virtual bool getResult(int column, short *value) override
  {
    int intValue;
    if (getResult(column, &intValue)) {
      *value = intValue;
      return true;
    } else
      return false;
  }

  virtual bool getResult(int column, int *value) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;

    const char *v = PQgetvalue(result_, row_, column);

    /*
     * booleans are mapped to int values
     */
    if (*v == 'f')
        *value = 0;
    else if (*v == 't')
        *value = 1;
    else
      *value = std::stoi(v);

    LOG_DEBUG(this << " result int " << column << " " << *value);

    return true;
  }

  virtual bool getResult(int column, long long *value) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;

    *value = std::stoll(PQgetvalue(result_, row_, column));

    LOG_DEBUG(this << " result long long " << column << " " << *value);

    return true;
  }

  virtual bool getResult(int column, float *value) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;


#ifdef WT_CPP_LIB_TO_CHARS
    std::string result_s = PQgetvalue(result_, row_, column);

    // try to convert with from_chars which has good round-trip properties
    auto returnValue = std::from_chars(result_s.data(), result_s.data() + result_s.size(), *value);

    // fall-back to boost::spirit for "out of range", e.g. subnormals in some implementations
    if (returnValue.ec == std::errc::result_out_of_range) {
      try {
        *value = std::stof(PQgetvalue(result_, row_, column));
      } catch (std::out_of_range&) {
        *value = convert<float>("stof", boost::spirit::float_, PQgetvalue(result_, row_, column));
      }
    } else if (returnValue.ec != std::errc()) {
      throw PostgresException(std::string("getResult: from_chars (float) of '") +
          result_s + "' failed, err: " + std::error_condition(returnValue.ec).message());
    }
#else
    try {
      *value = std::stof(PQgetvalue(result_, row_, column));
    } catch (std::out_of_range&) {
      *value = convert<float>("stof", boost::spirit::float_, PQgetvalue(result_, row_, column));
    }
#endif
    LOG_DEBUG(this << " result float " << column << " " << *value);

    return true;
  }

  virtual bool getResult(int column, double *value) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;


#ifdef WT_CPP_LIB_TO_CHARS
    std::string result_s = PQgetvalue(result_, row_, column);

    // try to convert with from_chars which has good round-trip properties
    auto returnValue = std::from_chars(result_s.data(), result_s.data() + result_s.size(), *value);

    // fall-back to boost::spirit for "out of range", e.g. subnormals in some implementations
    if (returnValue.ec == std::errc::result_out_of_range) {
      try {
        *value = std::stod(PQgetvalue(result_, row_, column));
      } catch (std::out_of_range&) {
        *value = convert<double>("stod", boost::spirit::double_, PQgetvalue(result_, row_, column));
      }
    } else if (returnValue.ec != std::errc()) {
      throw PostgresException(std::string("getResult: from_chars (float) of '") +
          result_s + "' failed, err: " + std::error_condition(returnValue.ec).message());
    }
#else
    try {
      *value = std::stod(PQgetvalue(result_, row_, column));
    } catch (std::out_of_range&) {
      *value = convert<double>("stod", boost::spirit::double_, PQgetvalue(result_, row_, column));
    }
#endif
    LOG_DEBUG(this << " result double " << column << " " << *value);

    return true;
  }

  virtual bool getResult(int column,
                         std::chrono::system_clock::time_point *value,
                         SqlDateTimeType type) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;

    std::string v = PQgetvalue(result_, row_, column);

    if (type == SqlDateTimeType::Date){
      std::istringstream in(v);
      in.imbue(std::locale::classic());
      in >> date::parse("%F", *value);
    } else {
      /*
       * Handle timezone offset. Postgres will append a timezone offset [+-]dd
       * if a column is defined as TIMESTAMP WITH TIME ZONE -- possibly
       * in a legacy table. If offset is present, subtract it for UTC output.
       */
      int offsetHour = 0;
      if(v.size() >= 3 && std::strchr("+-", v[v.size() - 3])){
          offsetHour = std::stoi(v.substr(v.size() - 3));
          v = v.substr(0, v.size() - 3);
      }
      std::istringstream in(v);
      in.imbue(std::locale::classic());
      in >> date::parse("%F %T", *value);
      *value -= std::chrono::hours{ offsetHour };
    }

    return true;
  }

  virtual bool getResult(int column, std::chrono::duration<int, std::milli> *value) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;

    std::string v = PQgetvalue(result_, row_, column);
    bool neg = false;
    if (!v.empty() && v[0] == '-') {
      neg = true;
      v = v.substr(1);
    }

    std::istringstream in(v);
    in.imbue(std::locale::classic());
    in >> date::parse("%T", *value);
    if (neg)
      *value = -(*value);

    return true;
  }

  virtual bool getResult(int column, std::vector<unsigned char> *value,
                         WT_MAYBE_UNUSED int size) override
  {
    if (PQgetisnull(result_, row_, column))
      return false;

    const char *escaped = PQgetvalue(result_, row_, column);

    std::size_t vlength;
    unsigned char *v = PQunescapeBytea((unsigned char *)escaped, &vlength);

    value->resize(vlength);
    std::copy(v, v + vlength, value->begin());
    PQfreemem(v);

    LOG_DEBUG(this << " result blob " << column << " (blob, size = " << vlength << ")");

    return true;
  }

  virtual std::string sql() const override {
    return sql_;
  }

private:
  struct Param {
    std::string value;
    bool isnull, isbinary;

    Param() : isnull(true), isbinary(false) { }
  };

  Postgres& conn_;
  std::string sql_;
  char name_[64];
  PGresult *result_;
  enum { NoFirstRow, FirstRow, NextRow, Done } state_;
  std::vector<Param> params_;

  int paramCount_;
  char **paramValues_;
  int *paramTypes_, *paramLengths_, *paramFormats_;

  long long lastId_;
  int row_, affectedRows_, columnCount_;

  void handleErr(int err, PGresult *result)
  {
    if (err != PGRES_COMMAND_OK && err != PGRES_TUPLES_OK) {
      std::string code;

      if (result) {
        char *v = PQresultErrorField(result, PG_DIAG_SQLSTATE);
        if (v)
          code = v;
      }

      throw PostgresException(PQerrorMessage(conn_.connection()), code);
    }
  }

  void setValue(int column, const std::string& value) {
    if (column >= paramCount_)
      throw PostgresException("Binding too many parameters");

    for (int i = (int)params_.size(); i <= column; ++i)
      params_.push_back(Param());

    params_[column].value = value;
    params_[column].isnull = false;
  }

  void convertToNumberedPlaceholders()
  {
    std::stringstream result;

    enum { Statement, SQuote, DQuote } state = Statement;
    int placeholder = 1;

    for (unsigned i = 0; i < sql_.length(); ++i) {
      switch (state) {
      case Statement:
        if (sql_[i] == '\'')
          state = SQuote;
        else if (sql_[i] == '"')
          state = DQuote;
        else if (sql_[i] == '?') {
          if (i + 1 != sql_.length() &&
              sql_[i + 1] == '?') {
            // escape question mark with double question mark
            result << '?';
            ++i;
          } else {
            result << '$' << placeholder++;
          }
          continue;
        }
        break;
      case SQuote:
        if (sql_[i] == '\'') {
          if (i + 1 == sql_.length())
            state = Statement;
          else if (sql_[i + 1] == '\'') {
            result << sql_[i];
            ++i; // skip to next
          } else
            state = Statement;
        }
        break;
      case DQuote:
        if (sql_[i] == '"')
          state = Statement;
        break;
      }
      result << sql_[i];
    }

    paramCount_ = placeholder - 1;
    sql_ = result.str();
  }
};

Postgres::Postgres()
  : conn_(nullptr),
    timeout_(0),
    maximumLifetime_(std::chrono::seconds{-1}),
    stopNotify_(true),
    isListener_(false),
    canSend_(true)
{ }

Postgres::Postgres(const std::string& db)
  : Postgres()
{
  if (!db.empty())
    connect(db);
}

Postgres::Postgres(const Postgres& other)
  : SqlConnection(other),
    conn_(NULL),
    timeout_(other.timeout_),
    maximumLifetime_(other.maximumLifetime_),
    stopNotify_(true),
    isListener_(false),
    canSend_(true)
{
  if (!other.connInfo_.empty())
    connect(other.connInfo_);
}

void Postgres::setMaximumLifetime(std::chrono::seconds seconds)
{
  maximumLifetime_ = seconds;
}

Postgres::~Postgres()
{
  clearStatementCache();
  if (conn_)
    PQfinish(conn_);
}

void Postgres::disconnect()
{
  if (conn_)
    PQfinish(conn_);

  conn_ = 0;

  std::vector<SqlStatement *> statements = getStatements();

  /* Evict also the statements -- the statements themselves can stay,
     only running statements behavior is affected (but we are dealing with
     that while calling disconnect) */
  for (std::size_t i = 0; i < statements.size(); ++i) {
    SqlStatement *s = statements[i];
    PostgresStatement *ps = dynamic_cast<PostgresStatement *>(s);
    ps->rebuild();
  }
}

void Postgres::setTimeout(std::chrono::microseconds timeout)
{
  timeout_ = timeout;
}

std::unique_ptr<SqlConnection> Postgres::clone() const
{
  return std::unique_ptr<SqlConnection>(new Postgres(*this));
}

bool Postgres::connect(const std::string& db)
{
  connInfo_ = db;
  conn_ = PQconnectdb(db.c_str());

  if (PQstatus(conn_) != CONNECTION_OK) {
    std::string error = PQerrorMessage(conn_);
    PQfinish(conn_);
    conn_ = nullptr;
    connectTime_ = std::chrono::steady_clock::time_point{};
    throw PostgresException("Could not connect to: " + error);
  } else
    connectTime_ = std::chrono::steady_clock::now();

  PQsetClientEncoding(conn_, "UTF8");

  return true;
}

bool Postgres::reconnect()
{
  LOG_INFO(this << " reconnecting...");

  if (conn_) {
    if (PQstatus(conn_) == CONNECTION_OK) {
      PQfinish(conn_);
    }

    conn_ = 0;
  }

  clearStatementCache();

  if (!connInfo_.empty()) {
    bool result = connect(connInfo_);

    if (result) {
      const std::vector<std::string>& statefulSql = getStatefulSql();
      for (unsigned i = 0; i < statefulSql.size(); ++i)
        executeSql(statefulSql[i]);
    }

    return result;
  } else
    return false;
}

std::unique_ptr<SqlStatement> Postgres::prepareStatement(const std::string& sql)
{
  if (PQstatus(conn_) != CONNECTION_OK)  {
    LOG_WARN("connection lost to server, trying to reconnect...");
    if (!reconnect()) {
      throw PostgresException("Could not reconnect to server...");
    }
  }

  return std::unique_ptr<SqlStatement>(new PostgresStatement(*this, sql));
}

void Postgres::executeSql(const std::string &sql)
{
  bool callExec = false;

  {
    std::lock_guard<std::mutex> l(sendQueueLock_);
    if (!isListener_ || canSend_) {
      canSend_ = false;
      callExec = true;
    } else {
      sendQueue_.push(sql);
    }
  }

  if (callExec) {
    exec(sql, true);
  }
}

/*
 * margin: a grace period beyond the lifetime
 */
void Postgres::checkConnection(std::chrono::seconds margin)
{
  if (maximumLifetime_ > std::chrono::seconds{0} && connectTime_ != std::chrono::steady_clock::time_point{}) {
    auto t = std::chrono::steady_clock::now();
    if (t - connectTime_ > maximumLifetime_ + margin) {
      LOG_INFO("maximum connection lifetime passed, trying to reconnect...");
      if (!reconnect()) {
        throw PostgresException("Could not reconnect to server...");
      }
    }
  }
}

void Postgres::exec(const std::string& sql, bool showQuery)
{
  checkConnection(std::chrono::seconds(0));

  if (PQstatus(conn_) != CONNECTION_OK)  {
    LOG_WARN("connection lost to server, trying to reconnect...");
    if (!reconnect()) {
      throw PostgresException("Could not reconnect to server...");
    }
  }

  if (showQuery && showQueries())
    LOG_INFO(sql);

  int err;

  err = PQsendQuery(conn_, sql.c_str());
  if (err != 1)
    throw PostgresException(PQerrorMessage(conn_));

  if (!isListener_) {
      if (timeout_ > std::chrono::microseconds{0}) {
      fd_set rfds;
      FD_ZERO(&rfds);
      FD_SET(PQsocket(conn_), &rfds);
      struct timeval timeout = toTimeval(timeout_);

      for (;;) {
        int result = select(FD_SETSIZE, &rfds, 0, 0, &timeout);

        if (result == 0) {
          LOG_ERROR("timeout while executing query");
          disconnect();
          throw PostgresException("Database timeout");
        } else if (result == -1) {
          if (errno != EINTR) {
            perror("select");
            throw PostgresException("Error waiting for result");
          } else {
            // EINTR, try again
          }
        } else {
          err = PQconsumeInput(conn_);
          if (err != 1)
            throw PostgresException(PQerrorMessage(conn_));

          if (PQisBusy(conn_) != 1)
            break;
        }
      }
    }

    checkForErrors();
  }
}

std::string Postgres::autoincrementType() const
{
  return "bigserial";
}

std::string Postgres::autoincrementSql() const
{
  return std::string();
}

std::vector<std::string>
Postgres::autoincrementCreateSequenceSql(WT_MAYBE_UNUSED const std::string& table,
                                         WT_MAYBE_UNUSED const std::string& id) const
{
  return std::vector<std::string>();
}

std::vector<std::string>
Postgres::autoincrementDropSequenceSql(WT_MAYBE_UNUSED const std::string& table,
                                       WT_MAYBE_UNUSED const std::string& id) const
{
  return std::vector<std::string>();
}

std::string Postgres::autoincrementInsertSuffix(const std::string& id) const
{
  return " returning \"" + id + "\"";
}

const char *Postgres::dateTimeType(SqlDateTimeType type) const
{
  switch (type) {
  case SqlDateTimeType::Date:
    return "date";
  case SqlDateTimeType::DateTime:
    return "timestamp";
  case SqlDateTimeType::Time:
    return "interval";
  }

  std::stringstream ss;
  ss << __FILE__ << ":" << __LINE__ << ": implementation error";
  throw PostgresException(ss.str());
}

const char *Postgres::blobType() const
{
  return "bytea not null";
}

bool Postgres::supportAlterTable() const
{
  return true;
}

bool Postgres::supportDeferrableFKConstraint() const
{
  return true;
}

bool Postgres::requireSubqueryAlias() const
{
  return true;
}

void Postgres::startTransaction()
{
  exec("start transaction", false);
}

void Postgres::commitTransaction()
{
  exec("commit transaction", false);
}

void Postgres::rollbackTransaction()
{
  exec("rollback transaction", false);
}

void Postgres::subscribe(const std::string& channel)
{
  char* escaped = PQescapeIdentifier(conn_, channel.c_str(), channel.length());
  if (!escaped) {
    throw PostgresException("Error while escaping channel name '" + channel + "': " + std::string(PQerrorMessage(conn_)));
  }

  std::string escapedChannel(escaped);
  PQfreemem(escaped);
  executeSqlStateful("LISTEN " + escapedChannel + ";");
}

void Postgres::notify(const std::string& channel, const std::string& message)
{
  char* escaped = PQescapeLiteral(conn_, message.c_str(), message.length());
  if (!escaped) {
    throw PostgresException("Error while escaping notification message for channel '" + channel + "': " + std::string(PQerrorMessage(conn_)));
  }

  std::string escapedMessage(escaped);
  PQfreemem(escaped);

  escaped = PQescapeIdentifier(conn_, channel.c_str(), channel.length());
  if (!escaped) {
    throw PostgresException("Error while escaping channel name '" + channel + "': " + std::string(PQerrorMessage(conn_)));
  }

  std::string escapedChannel(escaped);
  PQfreemem(escaped);

  executeSql("NOTIFY " + escapedChannel + ", " + escapedMessage + ";");
}

std::pair<std::string, std::string> Postgres::getNextNotify()
{
  PGnotify *notify = PQnotifies(conn_);

  fd_set rfds;

  /* Normaly the loop stops if we have a notification or if we must stop listening.
   * However, if we must stop listening but still have notification to send, we
   * ignore every notification received and send the notification one by one, until
   * all are sent.
   */
  while ((!notify || stopNotify()) && !(stopNotify() && canSend_)) {

    if (notify) {
      PQfreemem(notify);
    }
    checkConnection(std::chrono::seconds(0));

    if (PQstatus(conn_) != CONNECTION_OK)  {
      LOG_WARN("connection lost to server, trying to reconnect...");
      if (!reconnect()) {
        throw PostgresException("Could not reconnect to server...");
      }
    }

    FD_ZERO(&rfds);
    FD_SET(PQsocket(conn_), &rfds);
    int nfds = PQsocket(conn_) + 1;
    struct timeval timeout;
    struct timeval *timeoutPtr = nullptr;

    if (stopNotify() && !canSend_) {
      timeout = toTimeval(timeout_);
      timeoutPtr = &timeout;
    } else {
      FD_SET(stopPipe_->fd(), &rfds);
      nfds = std::max<int>(nfds, stopPipe_->fd() + 1);
    }

    int fdReady = select(nfds, &rfds, 0, 0, timeoutPtr);
    if (stopNotify() && canSend_) {
      break;
    }

    if (fdReady > 0) {
      int err = PQconsumeInput(conn_);
      if (err != 1) {
        throw PostgresException(PQerrorMessage(conn_));
      }

      notify = PQnotifies(conn_);

      if (PQisBusy(conn_) != 1) {
        checkForErrors();
        {
          std::lock_guard<std::mutex> l(sendQueueLock_);
          if (sendQueue_.empty()) {
            canSend_ = true;
          } else {
            exec(sendQueue_.front(), true);
            sendQueue_.pop();
          }
        }
      }
    } else if (fdReady == -1) {
      if (errno != EINTR) {
        perror("select");
        throw PostgresException("Error while listening for or sending notifications");
      }
    }
  }

  if (notify) {
    std::pair<std::string, std::string> result(std::string(notify->relname), std::string(notify->extra));
    PQfreemem(notify);
    return result;
  }

  return std::pair<std::string, std::string>("","");
}

void Postgres::setupNotify() {
  if (stopNotify_) {
    stopNotify_ = false;
    isListener_ = true;
    std::lock_guard<std::mutex> l(stopNotifyLock_);
    stopPipe_.reset(new StopPipe());
  }
}

void Postgres::stopListen() {
  if (!stopNotify_) {
    stopNotify_ = true;
    std::lock_guard<std::mutex> l(stopNotifyLock_);
    stopPipe_->stop();
  }
}

bool Postgres::stopNotify() {
  return stopNotify_;
}

Postgres::StopPipe::StopPipe()
{
#ifdef WT_WIN32
  pipe_ = INVALID_SOCKET;
  pipe_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (pipe_ == INVALID_SOCKET) {
    LOG_ERROR("Could not create socket for self-pipe trick due to "<< WSAGetLastError());
  }
#else
    int error = pipe(pipe_);
    if (error) {
      LOG_ERROR("Could not create pipe for self-pipe trick");
    }
#endif
}

Postgres::StopPipe::~StopPipe()
{
#ifdef WT_WIN32
  if (pipe_ != INVALID_SOCKET) {
    closesocket(pipe_);
  }
#else
    close(pipe_[0]);
    close(pipe_[1]);
#endif
}

void Postgres::StopPipe::stop()
{
#ifdef WT_WIN32
  if (pipe_ != INVALID_SOCKET) {
    closesocket(pipe_);
    pipe_ = INVALID_SOCKET;
  }
#else
  int msg = 0;
  write(pipe_[1], &msg, sizeof(int));
#endif
}

void Postgres::checkForErrors()
{
  std::string error;

  for (;;) {
    PGresult *result = PQgetResult(conn_);
    if (result == 0)
      break;

    int err = PQresultStatus(result);

    if (err != PGRES_COMMAND_OK && err != PGRES_TUPLES_OK) {
      error += PQerrorMessage(conn_);
    }
    PQclear(result);
  }
  if (!error.empty()) {
    throw PostgresException(error);
  }
}

    }
  }
}
