|
|
|
#pragma once
|
|
|
|
#include "merrors.h"
|
|
|
|
#include <libpq-fe.h>
|
|
|
|
#include <sqlite3.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <variant>
|
|
|
|
|
|
|
|
using michlib::int_cast;
|
|
|
|
using michlib::MString;
|
|
|
|
using michlib::pointer_cast;
|
|
|
|
|
|
|
|
class GenericCache
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual bool Put(const MString& key, const MString& value, size_t ttl) const = 0;
|
|
|
|
virtual std::pair<MString, bool> Get(const MString& key) const = 0;
|
|
|
|
virtual ~GenericCache() {}
|
|
|
|
};
|
|
|
|
|
|
|
|
class FakeCache: public GenericCache
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual bool Put([[maybe_unused]] const MString& key, [[maybe_unused]] const MString& value, [[maybe_unused]] size_t ttl) const override { return false; }
|
|
|
|
virtual std::pair<MString, bool> Get([[maybe_unused]] const MString& key) const override { return {"", false}; }
|
|
|
|
virtual ~FakeCache() override {}
|
|
|
|
};
|
|
|
|
|
|
|
|
class SQLiteCache: public GenericCache
|
|
|
|
{
|
|
|
|
sqlite3* db = nullptr;
|
|
|
|
|
|
|
|
public:
|
|
|
|
bool Init(const MString& name)
|
|
|
|
{
|
|
|
|
Close();
|
|
|
|
auto ret = sqlite3_open(name.Buf(), &db);
|
|
|
|
if(ret != SQLITE_OK)
|
|
|
|
{
|
|
|
|
Close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Create table
|
|
|
|
sqlite3_stmt* sqst;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
i = sqlite3_prepare_v2(db,
|
|
|
|
"CREATE TABLE IF NOT EXISTS `cache`('key' TEXT PRIMARY KEY ON CONFLICT REPLACE NOT NULL ON CONFLICT FAIL, 'value' BLOB NOT NULL ON CONFLICT FAIL, "
|
|
|
|
"'exptime' INTEGER NOT NULL ON CONFLICT FAIL) WITHOUT ROWID, STRICT;",
|
|
|
|
-1, &sqst, 0);
|
|
|
|
i = sqlite3_step(sqst);
|
|
|
|
if(i != SQLITE_DONE)
|
|
|
|
{
|
|
|
|
sqlite3_finalize(sqst);
|
|
|
|
Close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
sqlite3_finalize(sqst);
|
|
|
|
sqlite3_busy_timeout(db, 1000);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Close()
|
|
|
|
{
|
|
|
|
if(db != nullptr) sqlite3_close(db);
|
|
|
|
db = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual bool Put(const MString& key, const MString& value, size_t ttl) const override
|
|
|
|
{
|
|
|
|
if(!*this) return false;
|
|
|
|
sqlite3_stmt* sqst = nullptr;
|
|
|
|
int i = SQLITE_OK;
|
|
|
|
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "INSERT OR REPLACE into `cache` VALUES(?1,?2,?3);", -1, &sqst, 0);
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_text(sqst, 1, key.Buf(), -1, SQLITE_STATIC);
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_blob64(sqst, 2, value.Buf(), value.Len(), SQLITE_STATIC);
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 3, time(nullptr) + ttl);
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_step(sqst);
|
|
|
|
sqlite3_finalize(sqst);
|
|
|
|
|
|
|
|
return i == SQLITE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual std::pair<MString, bool> Get(const MString& key) const override
|
|
|
|
{
|
|
|
|
if(!*this) return {"", false};
|
|
|
|
|
|
|
|
sqlite3_stmt* sqst = nullptr;
|
|
|
|
int i = SQLITE_OK;
|
|
|
|
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "SELECT value from `cache` WHERE key=?1 AND exptime>?2;", -1, &sqst, 0);
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_text(sqst, 1, key.Buf(), -1, SQLITE_STATIC);
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 2, time(nullptr));
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_step(sqst);
|
|
|
|
if(i == SQLITE_ROW)
|
|
|
|
{
|
|
|
|
auto p = sqlite3_column_blob(sqst, 0);
|
|
|
|
auto sz = sqlite3_column_bytes(sqst, 0);
|
|
|
|
if(p != nullptr)
|
|
|
|
{
|
|
|
|
MString out(p, sz);
|
|
|
|
sqlite3_finalize(sqst);
|
|
|
|
return {std::move(out), true};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sqlite3_finalize(sqst);
|
|
|
|
return {"", false};
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual ~SQLiteCache() override
|
|
|
|
{
|
|
|
|
if(!*this) return;
|
|
|
|
|
|
|
|
sqlite3_stmt* sqst = nullptr;
|
|
|
|
int i = SQLITE_OK;
|
|
|
|
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_prepare_v2(db, "DELETE from `cache` WHERE exptime<?1;", -1, &sqst, 0);
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_bind_int64(sqst, 1, time(nullptr));
|
|
|
|
if(i == SQLITE_OK) i = sqlite3_step(sqst);
|
|
|
|
sqlite3_finalize(sqst);
|
|
|
|
sqlite3_close_v2(db);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
auto i = cachedesc.GetPos(':');
|
|
|
|
if(i == 0)
|
|
|
|
{
|
|
|
|
if(cachedesc == "no") return new FakeCache;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto name = cachedesc.SubStr(1, i - 1);
|
|
|
|
auto par = cachedesc.SubStr(i + 1, cachedesc.Len() - i);
|
|
|
|
|
|
|
|
if(name == "sqlite")
|
|
|
|
{
|
|
|
|
auto ret = new SQLiteCache;
|
|
|
|
ret->Init(par);
|
|
|
|
if(*ret) return 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;
|
|
|
|
}
|