You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 lines
11 KiB

#pragma once
#include "GPL.h"
#include <functional>
#include <libpq-fe.h>
#include <sqlite3.h>
#include <time.h>
#include <variant>
using michlib::GPL;
using michlib::int_cast;
using michlib::MString;
using michlib::pointer_cast;
class SQLiteConnection
{
public:
using DBType = sqlite3*;
using FuncType = std::function<void(DBType)>;
private:
static DBType db;
static size_t count;
static std::vector<FuncType> destructs;
public:
SQLiteConnection()
{
count++;
if(db == nullptr)
{
MString oldprefix = GPL.UsePrefix("SQLITE");
MString name = GPL.ParameterSValue("db", "");
GPL.UsePrefix(oldprefix);
auto ret = sqlite3_open(name.Buf(), &db);
if(ret != SQLITE_OK)
{
sqlite3_close(db);
db = nullptr;
}
}
}
SQLiteConnection([[maybe_unused]] const SQLiteConnection& sq): SQLiteConnection() {}
SQLiteConnection(SQLiteConnection&&) = default;
SQLiteConnection& operator=([[maybe_unused]] const SQLiteConnection& sq)
{
*this = {};
return *this;
}
SQLiteConnection& operator=(SQLiteConnection&&) = default;
~SQLiteConnection()
{
if(count == 0) michlib::errmessage("Destructor of SQLiteConnection called on count==0");
if(count > 1)
count--;
else
{
count = 0;
if(db != nullptr)
{
for(const auto& f: destructs) f(db);
sqlite3_close(db);
}
db = nullptr;
}
}
static void AddDestructor(FuncType&& f) { destructs.emplace_back(std::move(f)); }
operator DBType() const { return db; }
static DBType GetDB() { return db; }
explicit operator bool() const { return db != nullptr; }
};
class PostgreSQLConnection
{
public:
using DBType = PGconn*;
using FuncType = std::function<void(DBType)>;
private:
static DBType conn;
static size_t count;
static std::vector<FuncType> destructs;
public:
PostgreSQLConnection()
{
count++;
if(conn == nullptr)
{
MString oldprefix = GPL.UsePrefix("POSTGRES");
MString name = GPL.ParameterSValue("connection", "");
GPL.UsePrefix(oldprefix);
conn = PQconnectdb(name.Buf());
if(PQstatus(conn) != CONNECTION_OK)
{
michlib::errmessage(PQerrorMessage(conn));
PQfinish(conn);
conn = nullptr;
}
}
}
PostgreSQLConnection([[maybe_unused]] const PostgreSQLConnection& pq): PostgreSQLConnection() {}
PostgreSQLConnection(PostgreSQLConnection&&) = default;
PostgreSQLConnection& operator=([[maybe_unused]] const PostgreSQLConnection& pq)
{
*this = {};
return *this;
}
PostgreSQLConnection& operator=(PostgreSQLConnection&&) = default;
~PostgreSQLConnection()
{
if(count == 0) michlib::errmessage("Destructor of PostgreSQLConnection called on count==0");
if(count > 1)
count--;
else
{
count = 0;
if(conn != nullptr)
{
for(const auto& f: destructs) f(conn);
PQfinish(conn);
}
conn = nullptr;
}
}
static void AddDestructor(FuncType&& f) { destructs.emplace_back(std::move(f)); }
operator DBType() const { return conn; }
static DBType GetDB() { return conn; }
explicit operator bool() const { return conn != nullptr; }
};
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
{
static bool regdest;
SQLiteConnection db;
public:
bool Init()
{
if(!db) return false;
if(!regdest)
{
// 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);
return false;
}
sqlite3_finalize(sqst);
sqlite3_busy_timeout(db, 1000);
db.AddDestructor(
[](SQLiteConnection::DBType db)
{
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);
});
regdest = true;
}
return true;
}
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 = default;
explicit operator bool() const { return db != nullptr; }
};
class PostgreSQLHelpers
{
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<PGresult, PGResultRAIIDT>
{
public:
PGresultRAII() = default;
PGresultRAII(PGresult* res): std::unique_ptr<PGresult, PGResultRAIIDT>(res) {}
operator PGresult*() const { return get(); }
};
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;
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<const int1*>(src);
int1* pout = pointer_cast<int1*>(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()
{
if(!conn) return false;
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)
4 months ago
{
michlib::errmessage(PQresStatus(PQresultStatus(res)));
michlib::errmessage(PQerrorMessage(conn));
}
res = PQexec(conn, "SET client_min_messages=NOTICE;");
conn.AddDestructor([](PostgreSQLConnection::DBType conn) { PGresultRAII res = PQexec(conn, "DELETE FROM cache WHERE exptime<localtimestamp;"); });
regdest = true;
}
return true;
}
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()), sizeof(rinterval)};
int pfor[] = {0, 1, 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));
return false;
}
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};
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));
return {"", false};
}
else if(PQntuples(res) == 0)
return {"", false};
MString val(PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0));
return {std::move(val), true};
}
virtual ~PostgreSQLCache() override = default;
};
inline GenericCache* CreateCache(const MString& cachedesc)
{
auto i = cachedesc.GetPos(':');
auto name = i == 0 ? cachedesc : cachedesc.SubStr(1, i - 1);
auto par = i == 0 ? "" : cachedesc.SubStr(i + 1, cachedesc.Len() - i);
if(name == "no") return new FakeCache;
if(name == "sqlite")
{
auto ret = new SQLiteCache;
ret->Init();
if(*ret) return ret;
delete ret;
}
if(name == "postgre" || name == "postgres" || name == "postgresql")
{
auto ret = new PostgreSQLCache;
ret->Init();
if(*ret) return ret;
delete ret;
}
return nullptr;
}