diff --git a/include/cache.h b/include/cache.h index 16a6cdf..ad2fe1b 100644 --- a/include/cache.h +++ b/include/cache.h @@ -1,10 +1,13 @@ #pragma once -#include "MString.h" +#include "merrors.h" +#include #include #include #include +using michlib::int_cast; using michlib::MString; +using michlib::pointer_cast; class GenericCache { @@ -121,6 +124,129 @@ class SQLiteCache: public GenericCache 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 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]; + 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(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 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 Get(const MString& key) const override + { + if(!CheckCon()) return {"", false}; + + const char* params[] = {key.Buf()}; + 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); + 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 exptimeInit(par); + if(*ret) return ret; + delete ret; + } return nullptr; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a866fb7..e9b095d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,16 +10,17 @@ find_package(LibXml2 REQUIRED) find_package(SQLite3 REQUIRED) pkg_check_modules(JSONCPP REQUIRED jsoncpp) pkg_check_modules(BLOSC REQUIRED blosc) +pkg_check_modules(LIBPQ REQUIRED libpq) 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) add_executable(${EXENAME} ${srcs} ${ACTIONLISTINC} ${SOURCELISTINC}) 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) install(TARGETS ${EXENAME})