diff --git a/include/cache.h b/include/cache.h index 60620af..497b18c 100644 --- a/include/cache.h +++ b/include/cache.h @@ -253,12 +253,35 @@ class SQLiteCache: public GenericCache explicit operator bool() const { return db != nullptr; } }; -class PostgreSQLCache: public GenericCache +class PostgreSQLHelpers { - static bool regdest; + static constexpr time_t postgresepoch = 946648800; + + class PGResultRAIIDT + { + public: + // TODO: make static + void operator()(PGresult* res) { PQclear(res); } + }; + protected: PostgreSQLConnection conn; + // Convert Postgres binary representation of timestamp to Unix epoch seconds. Microseconds ignored + static time_t raw2epoch(time_t raw) { return Invert(raw) / 1000000 + postgresepoch; } + + // Convert Unix epoch time to Postres binary representation + static time_t epoch2raw(time_t epoch) { return Invert((epoch - postgresepoch) * 1000000); } + + class PGresultRAII: public std::unique_ptr + { + public: + PGresultRAII() = default; + PGresultRAII(PGresult* res): std::unique_ptr(res) {} + + operator PGresult*() const { return get(); } + }; + bool CheckCon() const { if(!*this) return false; @@ -270,14 +293,27 @@ class PostgreSQLCache: public GenericCache template static D Invert(D d) { using michlib::int1; - if(sizeof(D) <= 1) return d; - D out; - int1* pout = pointer_cast(&out); - int1* pin = pointer_cast(&d); - for(size_t i = 0; i < sizeof(D); i++) pout[sizeof(D) - i - 1] = pin[i]; + D out; + Invert(&d, &out, sizeof(D)); return out; } + static void Invert(const void* src, void* dst, size_t sz) + { + if(sz == 0) return; + const int1* pin = pointer_cast(src); + int1* pout = pointer_cast(dst); + for(size_t i = 0; i < sz; i++) pout[sz - i - 1] = pin[i]; + } + + public: + explicit operator bool() const { return conn != nullptr; } +}; + +class PostgreSQLCache: public GenericCache, public PostgreSQLHelpers +{ + static bool regdest; + public: bool Init() { @@ -285,31 +321,20 @@ class PostgreSQLCache: public GenericCache if(!regdest) { + PGresultRAII res; + // Create table + res = PQexec(conn, "SET client_min_messages=WARNING;"); + res = PQexec(conn, "CREATE TABLE IF NOT EXISTS cache(key TEXT PRIMARY KEY NOT NULL, value BYTEA, exptime TIMESTAMP(0) NOT NULL);"); + if(PQresultStatus(res) != PGRES_COMMAND_OK) { - auto* res = PQexec(conn, "SET client_min_messages=WARNING;"); - PQclear(res); - } - { - auto* res = PQexec(conn, "CREATE TABLE IF NOT EXISTS cache(key TEXT PRIMARY KEY NOT NULL, value BYTEA, exptime TIMESTAMP(0) NOT NULL);"); - if(PQresultStatus(res) != PGRES_COMMAND_OK) - { - michlib::errmessage(PQresStatus(PQresultStatus(res))); - michlib::errmessage(PQerrorMessage(conn)); - } - PQclear(res); - } - { - auto* res = PQexec(conn, "SET client_min_messages=NOTICE;"); - PQclear(res); + michlib::errmessage(PQresStatus(PQresultStatus(res))); + michlib::errmessage(PQerrorMessage(conn)); } - conn.AddDestructor( - [](PostgreSQLConnection::DBType conn) - { - auto* res = PQexec(conn, "DELETE FROM cache WHERE exptime(ttl); michlib::int8 rinterval = Invert(interval); const char* params[] = {key.Buf(), value.Buf(), pointer_cast(&rinterval)}; - int plens[] = {int_cast(key.Len()), int_cast(value.Len()), 8}; + int plens[] = {int_cast(key.Len()), int_cast(value.Len()), sizeof(rinterval)}; int pfor[] = {0, 1, 1}; - auto* res = PQexecParams(conn, - "INSERT INTO cache(key,value,exptime) VALUES($1,$2,localtimestamp + ($3::bigint ||' seconds')::interval)" - "ON CONFLICT(key) DO UPDATE SET value=EXCLUDED.value, exptime=EXCLUDED.exptime;", - 3, nullptr, params, plens, pfor, 1); + PGresultRAII res = PQexecParams(conn, + "INSERT INTO cache(key,value,exptime) VALUES($1,$2,localtimestamp + ($3::bigint ||' seconds')::interval)" + "ON CONFLICT(key) DO UPDATE SET value=EXCLUDED.value, exptime=EXCLUDED.exptime;", + 3, nullptr, params, plens, pfor, 1); if(PQresultStatus(res) != PGRES_COMMAND_OK) { michlib::errmessage(PQresStatus(PQresultStatus(res))); michlib::errmessage(PQerrorMessage(conn)); - PQclear(res); return false; } - PQclear(res); return true; } @@ -349,28 +372,21 @@ class PostgreSQLCache: public GenericCache int plens[] = {int_cast(key.Len())}; int pfor[] = {0}; - auto* res = PQexecParams(conn, "SELECT value from cache WHERE key=$1::text AND exptime>localtimestamp;", 1, nullptr, params, plens, pfor, 1); + PGresultRAII res = PQexecParams(conn, "SELECT value from cache WHERE key=$1::text AND exptime>localtimestamp;", 1, nullptr, params, plens, pfor, 1); if(PQresultStatus(res) != PGRES_TUPLES_OK) { michlib::errmessage(PQresStatus(PQresultStatus(res))); michlib::errmessage(PQerrorMessage(conn)); - PQclear(res); return {"", false}; } else if(PQntuples(res) == 0) - { - PQclear(res); return {"", false}; - } MString val(PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0)); - PQclear(res); return {std::move(val), true}; } virtual ~PostgreSQLCache() override = default; - - explicit operator bool() const { return conn != nullptr; } }; inline GenericCache* CreateCache(const MString& cachedesc)