Browse Source

PostgreSQL backend for the cache

lintest
Michael Uleysky 8 months ago
parent
commit
8bfa07b9c9
  1. 135
      include/cache.h
  2. 5
      src/CMakeLists.txt

135
include/cache.h

@ -1,10 +1,13 @@
#pragma once #pragma once
#include "MString.h" #include "merrors.h"
#include <libpq-fe.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <time.h> #include <time.h>
#include <variant> #include <variant>
using michlib::int_cast;
using michlib::MString; using michlib::MString;
using michlib::pointer_cast;
class GenericCache class GenericCache
{ {
@ -121,6 +124,129 @@ class SQLiteCache: public GenericCache
explicit operator bool() const { return db != nullptr; } explicit operator bool() const { return db != nullptr; }
}; };
class PostgreSQLCache: public GenericCache
{
PGconn* conn = nullptr;
bool CheckCon() const
{
if(!*this) return false;
if(PQstatus(conn) == CONNECTION_OK) return true;
PQreset(conn);
return PQstatus(conn) == CONNECTION_OK;
}
template<class D> static D Invert(D d)
{
using michlib::int1;
if(sizeof(D) <= 1) return d;
D out;
int1* pout = pointer_cast<int1*>(&out);
int1* pin = pointer_cast<int1*>(&d);
for(size_t i = 0; i < sizeof(D); i++) pout[sizeof(D) - i - 1] = pin[i];
return out;
}
public:
bool Init(const MString& name)
{
Close();
conn = PQconnectdb(name.Buf());
if(PQstatus(conn) != CONNECTION_OK)
{
michlib::errmessage(PQerrorMessage(conn));
Close();
return false;
}
// Create table
if(false)
{
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);
Close();
}
else
PQclear(res);
}
return true;
}
void Close()
{
if(conn != nullptr) PQfinish(conn);
conn = nullptr;
}
virtual bool Put(const MString& key, const MString& value, size_t ttl) const override
{
if(!CheckCon()) return false;
auto interval = michlib::int_cast<michlib::int8>(ttl);
michlib::int8 rinterval = Invert(interval);
const char* params[] = {key.Buf(), value.Buf(), pointer_cast<const char*>(&rinterval)};
int plens[] = {int_cast<int>(key.Len()), int_cast<int>(value.Len()), 8};
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);
if(PQresultStatus(res) != PGRES_COMMAND_OK)
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
PQclear(res);
return false;
}
PQclear(res);
return true;
}
virtual std::pair<MString, bool> Get(const MString& key) const override
{
if(!CheckCon()) return {"", false};
const char* params[] = {key.Buf()};
int plens[] = {int_cast<int>(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);
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
{
if(!CheckCon()) return;
auto* res = PQexec(conn, "DELETE FROM cache WHERE exptime<localtimestamp;");
PQclear(res);
Close();
}
explicit operator bool() const { return conn != nullptr; }
};
inline GenericCache* CreateCache(const MString& cachedesc) inline GenericCache* CreateCache(const MString& cachedesc)
{ {
auto i = cachedesc.GetPos(':'); auto i = cachedesc.GetPos(':');
@ -140,6 +266,13 @@ inline GenericCache* CreateCache(const MString& cachedesc)
if(*ret) return ret; if(*ret) return ret;
delete ret; delete ret;
} }
if(name == "postgre" || name == "postgres" || name == "postgresql")
{
auto ret = new PostgreSQLCache;
ret->Init(par);
if(*ret) return ret;
delete ret;
}
return nullptr; return nullptr;
} }

5
src/CMakeLists.txt

@ -10,16 +10,17 @@ find_package(LibXml2 REQUIRED)
find_package(SQLite3 REQUIRED) find_package(SQLite3 REQUIRED)
pkg_check_modules(JSONCPP REQUIRED jsoncpp) pkg_check_modules(JSONCPP REQUIRED jsoncpp)
pkg_check_modules(BLOSC REQUIRED blosc) pkg_check_modules(BLOSC REQUIRED blosc)
pkg_check_modules(LIBPQ REQUIRED libpq)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
include_directories(${JSONCPP_INCLUDE_DIRS} ${BLOSC_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIRS} ${SQLite3_INCLUDE_DIRS}) include_directories(${JSONCPP_INCLUDE_DIRS} ${BLOSC_INCLUDE_DIRS} ${LIBPQ_INCLUDE_DIRS} ${LIBXML2_INCLUDE_DIRS} ${SQLite3_INCLUDE_DIRS})
file(GLOB srcs CONFIGURE_DEPENDS *.cpp) file(GLOB srcs CONFIGURE_DEPENDS *.cpp)
add_executable(${EXENAME} ${srcs} ${ACTIONLISTINC} ${SOURCELISTINC}) add_executable(${EXENAME} ${srcs} ${ACTIONLISTINC} ${SOURCELISTINC})
target_include_directories(${EXENAME} PRIVATE ../michlib/michlib ${CMAKE_CURRENT_BINARY_DIR}/../include) target_include_directories(${EXENAME} PRIVATE ../michlib/michlib ${CMAKE_CURRENT_BINARY_DIR}/../include)
target_link_libraries(${EXENAME} ${linker_options} ${netcdf} OpenMP::OpenMP_CXX CURL::libcurl ${JSONCPP_LINK_LIBRARIES} ${BLOSC_LINK_LIBRARIES} LibXml2::LibXml2 SQLite::SQLite3 teos) target_link_libraries(${EXENAME} ${linker_options} ${netcdf} OpenMP::OpenMP_CXX CURL::libcurl ${JSONCPP_LINK_LIBRARIES} ${BLOSC_LINK_LIBRARIES} ${LIBPQ_LINK_LIBRARIES} LibXml2::LibXml2 SQLite::SQLite3 teos)
set_target_properties(${EXENAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) set_target_properties(${EXENAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
install(TARGETS ${EXENAME}) install(TARGETS ${EXENAME})

Loading…
Cancel
Save